---
name: ringcentral-oauth-app
description: >-
  Creates or signs in to a RingCentral developer account and registers a REST
  API app in the Developer Console to obtain a client ID and secret — with the
  public-vs-private choice that cannot be changed later, the Redirect URI
  list, the permission catalogue and its approval tickets, the "Issue refresh
  tokens" toggle that is off by default, the retired sandbox and its
  replacement, the AT&T Office@Hand / Verizon API host, and per-API-group rate
  limits. Use when asked to get RingCentral OAuth credentials, create a
  RingCentral app, work out what production access requires, add a restricted
  permission like ReadCallRecording, rotate a client secret, or fix a
  RingCentral error such as `OAU-109 Redirect URIs do not match`, `OAU-146
  Invalid client credentials`, `CMN-401`/`InsufficientPermissions`, or a
  connection that stops refreshing after a week.
---

# RingCentral OAuth2 App Registration

Get a working RingCentral OAuth2 client — a Developer Console login on a RingCentral account, a **REST API app**, a
Redirect URI list, a permission set, and a client ID and secret — for a platform that connects many customers'
RingCentral accounts.

Four things about this portal cost more than they look, and the first is the one everybody gets wrong.

**There is no sandbox any more, and no graduation step.** RingCentral retired the free developer sandbox and the
`platform.devtest.ringcentral.com` host; that hostname no longer resolves, and the old `guide/basics/sandbox` and
`guide/getting-started/graduate-app` documentation pages are gone. Runbooks that tell you to build in sandbox and
then "graduate to production" describe a process that does not exist. See §0 — this is a premise worth checking
before you plan anything around it.

**Application Type — public vs private — is set at creation and cannot be edited afterwards.** Pick private for a
multi-tenant connector and you will be building a second app.

**Refresh tokens are off by default.** "Issue refresh tokens" is a checkbox in the app's settings, and without it
every customer connection dies with the access token. RingCentral's refresh tokens are also **single-use** and live
**seven days**, not ninety — a connector that is quiet for a week is a connector that needs every customer back in
the authorize flow.

**The authorize URL has no `scope` parameter.** The app's configured permission list *is* the consent screen. You
cannot ask one customer for less, and adding a permission in the console silently widens what every new customer is
asked to approve.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name** and **Display name** | App name is internal only; Display name is what users see in the OAuth consent screen (§3) |
| **Which RingCentral account owns the app** | Needs Developer Console access on that account (§2) |
| **Public or private?** | **Cannot be changed after creation** — a multi-tenant connector is public (§3) |
| **Every Redirect URI** | One per data center, exact match, no trailing-slash drift (§4) |
| **Permission set** | What the connector actually calls; note which ones say "requires permission" (§5) |
| **Confidential or client-side?** | Decides Basic-auth vs PKCE at the token exchange (§3, §6) |
| **App icon, primary contact, company website** | Contact is who RingCentral emails about the app; icon may be required by app type (§3) |
| **Will you list in the App Gallery?** | Asked at creation; drives a separate marketing review later (§7) |
| **Must AT&T Office@Hand / Verizon customers work?** | They are on a different API host entirely (§7) |
| **Privacy policy, terms, support and documentation URLs** | Required for an App Gallery listing (§7) |

## Quick Start

1. Read §0 first — the sandbox/graduation model you may be expecting is gone.
2. Confirm a **new app** is needed; a new client ID orphans every existing customer connection (§1).
3. Sign in to the Developer Console on an account that grants **Developer Portal Access** (§2).
4. **Create App → REST API App**, then set **Application Type** carefully — it is permanent (§3).
5. Enable **3-legged OAuth** and tick **Issue refresh tokens** (§3).
6. Enter every **Redirect URI**, exactly (§4).
7. Select permissions; write a real justification for any marked "requires permission" (§5).
8. Capture the client ID and secret (§6).
9. Verify from a **second** RingCentral account, and force **two consecutive refreshes** (§8).
10. Hand the credentials over — never commit them (§9).

## Platform state (verified 2026-09-20 — re-verify before trusting)

- **The developer sandbox is retired.** `platform.devtest.ringcentral.com` has no DNS record at all. The
  authorization-request guide now lists exactly one environment — "Production,
  `https://platform.ringcentral.com/restapi/oauth/authorize`" — where it used to list two. The doc paths
  `/guide/basics/sandbox` and `/guide/getting-started/graduate-app` return HTTP 200 but render the documentation
  homepage, which on this SPA is what a **deleted page** looks like. Do not cite either as evidence they exist.
- **What replaced it is a paid, separate RingCentral account.** The developer pricing page sells a *Development
  environment*, subtitled "Watermarked sandbox", at **$12/month** ($10 annually) for two included phone extensions
  plus $5/month per additional extension, "for ISV partners and RingEX customers looking for a separate development
  environment to build and test business applications without accessing, impacting, or modifying production data."
  The same page states plainly that a RingEX plan "includes access to our APIs in your production environment."
- **App types you can create today** are **REST API App** (what a connector needs), **Bot Add-in**, and
  **Notification Add-in**, which is deprecated and which "many developers may no longer have access to."
- **Auth methods offered at creation** are the **3-legged OAuth flow** and the **JWT auth flow**. "The password
  grant type has been deprecated." JWT authenticates a single service user and RingCentral says explicitly it "is
  not designed to scale to support the need to authenticate a large number of users" — it is not a multi-tenant
  option.
- **Application Type (public/private) cannot be edited after the app has been created.** Public means "any
  RingCentral credential can be used to login to your application"; private means only credentials on your own
  account. RingCentral notes public apps "are subjected to greater scrutiny through the development process."
- **Access token ~1 hour, refresh token 7 days, refresh tokens are single-use.** The docs' table says 1 hour /
  7 days; sample responses show `expires_in: 7199` and `refresh_token_expires_in: 604799`. Read the fields, do not
  hardcode either.
- **Refresh tokens are not issued unless you enable them**: "By default, refresh tokens are not issued, so if you
  observe that a refresh token was not returned along with access token, edit your app's settings."
- **The authorize endpoint accepts no `scope` parameter.** Documented parameters are `response_type`, `client_id`,
  `redirect_uri`, `state`, `code_challenge`, `code_challenge_method`, `brand_id` and `display`.
- **PKCE is supported and recommended but not mandatory** for a server-side confidential client, which exchanges the
  code with HTTP Basic `client_id:client_secret`.
- **Two API hosts exist, and they are not sandbox and production.** Commercial is
  `https://platform.ringcentral.com/`; **segregated** is `https://platform.ringcentral.biz/`, and it serves **AT&T
  Office@Hand and Verizon** customers, who are in "a completely segregated environment" for regulatory reasons.
- **Rate limits are per (authenticated user, application ID) and per API group** — Light 50, Medium 40, Heavy 10 and
  Auth 5 requests/user/minute by default, customizable per app.

If the Developer Console or the docs do not look like this, stop and report what you actually see rather than
clicking on.

## 0. The premise to check first: sandbox and "graduation"

Most RingCentral runbooks, blog posts and forum answers older than 2025 describe this sequence: create an app, it
lands in **sandbox** with a sandbox client ID against `platform.devtest.ringcentral.com`, exercise every permission
with at least a handful of API calls, then request **graduation**, wait for review, and receive a *second,
different* client ID and secret for production.

**None of that is live.** The sandbox host does not resolve. The graduation and sandbox documentation pages have
been deleted. The authorization guide, which used to carry a two-row environment table, now carries one row.

What this means in practice:

- **You get one credential pair, and it works against production immediately.** There is no second pair to swap in
  at launch, and no "which environment is this secret for?" class of bug.
- **The scheduling risk moved.** It is no longer a formal graduation review. What can still block or delay you is
  (a) **restricted-permission approval tickets** (§5), (b) **App Gallery listing review**, if you want to be listed
  (§7), and (c) **acquiring a test account at all**, because testing now means either a paid Development environment
  or a live RingEX account (§2).
- **Testing against production is the default posture.** Budget for it: a connector under development is making real
  calls against a real account's real call logs, contacts and messages.

Two caveats, stated honestly. First, RingCentral announced this transition through its developer **community**
posts, which are login-gated — I could not read them directly, so treat any specific retirement *date* you are
told as unverified. The retirement itself is not in doubt: the host is gone and the docs are gone. Second, I could
not sign in to the Developer Console, so **if the console still shows a Sandbox/Production toggle on an existing
older app, believe the console and report it** — legacy apps created under the old model may carry artefacts the
current documentation no longer describes.

## 1. Decide: reuse the existing app, or register a new one

A new app means a **new client ID, and every existing customer connection is bound to the old one** — every customer
would have to re-authorize. Reuse the existing app for: adding a Redirect URI, adding or removing a permission,
rotating a compromised secret, enabling refresh tokens, or diagnosing an authorization failure.

Register a **new** app only when the user explicitly wants one: a replacement for a compromised app, a separate app
for a different product, or — the one RingCentral forces on you — an app that was created as **private** and now
needs to serve customers outside your own account, because Application Type cannot be edited (§3). Say which path
you are taking before you touch anything.

RingCentral is explicit that you do not need one app per API: "A single RingCentral application can interact with
all the APIs on the RingCentral platform. That means there is no need to register multiple apps." Voice, Team
Messaging, Video and account data all live behind one client ID.

## 2. Account: sign in, and the Developer Portal permission

Sign in at `https://developers.ringcentral.com/login`. There is no standalone developer account any more — the
Developer Console is reached with a **RingCentral account** login, which now means a RingEX subscription or a paid
Development environment (§0).

**If the "Create App" button is visible but disabled, that is the diagnosis**: "your account lacks the permission
required to create an app. Contact your account's administrator to request this permission." Do not hunt for another
route.

To add a colleague, they must first exist as a **user on your RingCentral account** (Admin Console → Users → Add
User), and their assigned role needs the **Developer Portal Access** permission under the **Features** permission
group. They get no invitation email; they simply become able to log in. Console roles are then:

| Role | Can do |
| --- | --- |
| **Developer Admin** | Manage any app, manage developers, manage developer JWT credentials |
| **Developer** | Manage only apps they created, and their own JWT credentials |
| **Audit-user** | View apps and profiles only |

Only a Developer Admin can assign roles; the **Organization** tab lists who the Developer Admins are.

Hand control back to the user for anything only they can do: account signup and payment details, email
verification, 2FA, accepting the API License Agreement, or granting Developer Portal Access. Do not retry a blocked
step in a loop.

**If this session has no browser automation** (the usual case for a CLI or cloud run), do not pretend to click. Hand
the user an exact, ordered click path with the literal values to paste (§4 Redirect URIs, §5 permission list), then
continue once they report back with the client ID.

## 3. Create the app, and the choices that are permanent

**Developer Console → Apps → Create App → REST API App.**

- **App name** — internal only, "never displayed publically," so `Acme Connector (prod)` is fine and useful.
- **Display name** — what customers see, including in the OAuth consent screen. This is the name a customer must
  recognise when they are asked to authorize; get it right.
- **App icon** — may or may not be required depending on app type; bots and add-ins always need one.
- **Primary contact** — "designate a individual to be responsible for receiving and responding to important
  communications relating to this app." Use a monitored role address, not a person who may leave.
- **Will you be promoting your app?** — RingCentral asks whether you intend to list in the App Gallery. The answer
  is "for internal-use only" and does not itself start a review.

Then the choices that are expensive:

**Application Type: public or private. Permanent.** RingCentral's framing is about *whose credentials can log in*,
not about client confidentiality:

- **Public** — "any RingCentral credential can be used to login to your application." This is the only correct
  answer for a platform connecting other organizations' accounts. RingCentral names exactly this case: "You are a
  partner building an application that you will market and sell to RingCentral customers."
- **Private** — "only credentials belonging to your account can be used." For internal tools and CPaaS-style usage
  where you are consuming your own RingCentral service.

The docs say it twice and so will I: **"You cannot edit Application Type after your app has been created."** A
private app pointed at customers fails at *login*, before consent, for everyone outside your account.

**Authentication: 3-legged OAuth.** Tick it. JWT is the other option and it is for a single service user; RingCentral
says outright it "is not designed to scale to support the need to authenticate a large number of users."

**Issue refresh tokens: tick it.** This is the single most missed setting on the platform. "By default, refresh
tokens are not issued." Without it, every customer connection dies about an hour after they connect and the only
symptom is a token response with no `refresh_token` in it.

**Platform Type** decides what else the app can do and how the token exchange authenticates. The documentation uses
two vocabularies that do not fully agree — an older set (`Server/Web` "most common", `Web Browser (Javascript)`,
`Server/Bot`, `Server/No UI`) and a newer one referenced in the refresh-token guide (`Client-side web app`). **Trust
the console's current wording over both.** What matters is the behavioural split:

- A **server-side** app is a confidential client: it sends `Authorization: Basic base64(client_id:client_secret)` at
  the token endpoint. This is what a hosted multi-tenant connector is.
- A **client-side web app** does not send an Authorization header at all — "in order to keep the app's client secret
  hidden from the request/response" — and instead sends `client_id` in the request body.

Picking the client-side type for a server-side connector, or vice versa, produces `OAU-116 Invalid authorization
method` or `OAU-123 Invalid Authorization header value` at the exchange, which reads like a credential problem and
is not one.

## 4. Redirect URIs

RingCentral matches these exactly: "the `redirect_uri` must exactly match at least one of the Redirect URIs provided
by the developer when the app was created." Register **every** callback host your platform can send a customer back
to. For Unified.to these are one per data center; confirm the current list with the platform owner rather than
assuming:

```
https://api.unified.to/oauth/code          # us (default)
https://api-eu.unified.to/oauth/code       # eu
https://api-au.unified.to/oauth/code       # au
https://api-dev.unified.to/oauth/code      # dev
```

Notes that decide whether a connect flow works:

- **The console field is plural and the docs say "one of"** — multiple URIs are supported. RingCentral does not
  publish a maximum; if the console refuses to add another, report the number it stopped at rather than guessing.
- **Exact match means exact.** A trailing slash, `http` instead of `https`, a differing case in the path, or an
  extra query parameter is a different URI. RingCentral surfaces this as **`OAU-109 Redirect URIs do not match`**
  (HTTP 403), and an app with no registered URI at all as **`OAU-113 No redirect uri is registered for the client`**.
- **A bad `redirect_uri` can also fail earlier**: "in some cases, HTTP 400 may be returned on the
  `/restapi/oauth/authorize` call. This can happen when the client provides an invalid redirect URI."
- The failure appears **at connect time, not at save time**. Nothing in the console tells you the app is broken.
- **For AT&T Office@Hand and Verizon customers the login and API hosts differ** (`login.ringcentral.biz` /
  `platform.ringcentral.biz`) but the *redirect* still comes back to your own callback host — see §7.

## 5. Permissions (RingCentral's word for scopes)

RingCentral calls them **app permissions** or **scopes** interchangeably. "Required scopes are generally declared at
the application registration stage and confirmed by the user during the authorization stage."

### The catalogue

This is the current published list, verbatim in naming. Note the composition column — several permissions imply
others, so you often need fewer entries than you think.

| Permission | What it covers | Access type | Includes |
| --- | --- | --- | --- |
| `A2P SMS` | Sending SMS in large numbers | Special operation | |
| `Accounts` | Create, view, update, delete accounts | CRUD | `EditAccounts` |
| `AI` | Analyze audio and text; view/update analysis info | Read only | |
| `Analytics` | Call analytics data via the Analytics product | Read only | |
| `CallControl` | Manipulate and control calls in progress | Special operation | |
| `Contacts` | Create, view, edit, delete personal contacts | CRUD | `ReadContacts` |
| `ControlWebinars` | | Special operation | |
| `DirectRingOut` | Direct (one-legged) ring-out calls — **available on request** | Special operation | |
| `EditAccounts` | View and update account info | Read and Update | `ReadAccounts`, `EditExtensions` |
| `EditCallLog` | View and update call logs | Read and Update | `ReadCallLog` |
| `EditCustomData` | View and update client custom data | Read and Update | |
| `EditExtensions` | View and update extension info, assigned numbers, devices, settings | Read and Update | |
| `EditMessages` | View and update messages | Read and Update | `ReadMessages` |
| `EditPaymentInfo` | View and update account billing settings | Read and Update | |
| `EditPresence` | Get and modify presence | Read and Update | `ReadPresence` |
| `EditReportingSettings` | Call reporting settings — **available on request** | Read and Update | |
| `EditUserCredentials` | Manage a user's login credentials/password — **available on request** | Read and Update | |
| `EditWebinars` | View and update webinars and sessions | Read and Update | |
| `Faxes` | Sending and receiving faxes | Special operation | `ReadMessages` |
| `Glip` | Read and post messages, read and manage chats | Read and Update | |
| `InternalMessages` | Intra-company pager messages | Special operation | `ReadMessages` |
| `Meetings` | CRUD meetings via **RingCentral Meetings** | CRUD | |
| `ReadAccounts` | View account info | Read only | |
| `ReadCallLog` | View call logs | Read only | |
| `ReadCallRecording` | **Downloading call recording content** | Read only | `ReadCallLog` |
| `ReadClientInfo` | Client app registered attributes and helper info | Special operation | |
| `ReadContacts` | View personal contacts | Read only | |
| `ReadMessages` | View messages | Read only | |
| `ReadPresence` | Get presence | Read only | |
| `ReadWebinars` | | Special operation | |
| `RingOut` | Two-legged ring-out calls | Special operation | |
| `RoleManagement` | Edit and assign user roles — **available on request** | Special operation | |
| `SMS` | Sending and receiving SMS | Special operation | `ReadMessages` |
| `SubscriptionWebhook` | Subscribing to and managing webhook notification preferences | Special operation | |
| `TeamMessaging` | Post messages; view, edit, delete Team Messaging data | | |
| `Video` | CRUD meetings via **RingCentral Video** | CRUD | |
| `Voicemail` | Delivering voicemail to multiple internal recipients | Special operation | |
| `VoipCalling` | Register as a VoIP device and make VoIP calls | Special operation | |

Three corrections worth carrying:

1. **The webhook permission is `SubscriptionWebhook`**, not `WebhookSubscriptions`.
2. **`Meetings` and `Video` are different products.** `Video` is RingCentral Video (`/rcvideo/...`); `Meetings` is
   the older RingCentral Meetings product. Picking the wrong one authorizes cleanly and 403s at the API.
3. **`ReadCallRecording` is separate from `ReadCallLog`** and is the one that matters for audio. See below.

### The recording trap, in RingCentral's own table

The Call Log access-control guide spells out the split, and it is the most common scope mistake on this platform:

| Endpoint | Needs `ReadCallLog` | Needs `ReadCallRecording` |
| --- | --- | --- |
| `/v1.0/account/{accountId}/call-log` | YES | NO |
| `/v1.0/account/{accountId}/extension/{extensionId}/call-log` | YES | NO |
| `/v1.0/account/{accountId}/extension/{extensionId}/active-calls` | YES | NO |
| `/v1.0/account/{accountId}/recording/{recordingId}` (metadata) | YES | YES |
| `/v1.0/account/{accountId}/recording/{recordingId}/content` (**audio**) | NO | YES |

An app holding only `ReadCallLog` lists calls perfectly and then fails every recording download with HTTP 403:

```json
{ "errorCode": "InsufficientPermissions",
  "message": "In order to call this API endpoint, application needs to have [ReadCallLog] permission",
  "errors": [ { "errorCode": "CMN-401", "permissionName": "ReadCallLog" } ] }
```

The shape is the same for any missing permission — read `permissionName`, not the prose.

### "Requires permission": the approval you can actually get blocked on

Some permissions carry **"(requires permission)"** in the console. RingCentral: "When you add a restricted app scope
to your app, you will be prompted to provide a justification for why your app requires the ability to perform the
actions associated with that app scope. Then, when you save the settings of that app, a request will be filed with
the support to review your petition, and approve (or reject) your request accordingly." The registration guide adds
that "a support ticket will be filed automatically with the developer support team."

- Separately, four permissions are marked **"Available on request"** in the catalogue — `DirectRingOut`,
  `EditReportingSettings`, `EditUserCredentials`, `RoleManagement`. Treat those as blocked until granted.
- **RingCentral publishes no turnaround time for these tickets.** Do not promise one. This is the item to raise with
  the user early, because it is the remaining schedule risk now that graduation is gone (§0).
- "To maximize the likelihood of your request being approved, please provide a detailed, well-written response to
  the justification prompt." Write one concrete sentence per permission naming the feature it powers, and request
  the narrow set: RingCentral's own guidance is "For security, request only permissions your application requires."

### Application permission is only half of it

This is the RingCentral equivalent of admin-vs-user consent, and it is not a console setting:

> "Setting an application's scope does not by itself confer upon a user of that application the ability to perform
> the associated action… in order for a user of an application to perform a given operation, the app must declare the
> corresponding scope, **and the user must have been assigned a role that possesses the corresponding user
> permission as well**."

So the effective access is the **intersection** of your app's permissions and the authorizing user's RingCentral
role. Concretely:

- **Account-wide reads use `account/~`** — the company call log, the company directory, account-level recordings.
  A plain extension authorizing your app produces a valid token that then returns **`CMN-402 Administrator
  permission required`** (403) on those paths.
- **Reaching another extension's data** as a non-privileged user returns **`CMN-404 Attempt to access another
  extension`**.
- **There is no flag in the token response that tells you the user is an admin.** You discover it at the API.

If the product needs company-wide data, the install story has to be "an administrator connects the account", and
that needs saying to customers up front — not discovered by them when the first sync returns nothing.

### You cannot narrow permissions per authorization

The authorize endpoint takes no `scope` parameter. The consent screen shows what the app is configured with, full
stop. Two consequences: adding a permission in the console changes what *every* new customer is asked to approve,
and you cannot offer a "read-only tier" of the same app. The token response does return a `scope` string (e.g.
`"AccountInfo CallLog ExtensionInfo Messages SMS"`) which you can inspect after the fact.

## 6. Capture the credentials

From the app's credentials page in the Developer Console. Capture:

- **Client ID** and **Client Secret** — "your app will be provisioned app credentials in the form of a Client ID and
  Client Secret." One pair; there is no development/production split any more (§0).
- Endpoints, commercial environment:
  - authorize — `https://platform.ringcentral.com/restapi/oauth/authorize`
  - token — `https://platform.ringcentral.com/restapi/oauth/token`
  - revoke — `https://platform.ringcentral.com/restapi/oauth/revoke`
- Endpoints, segregated environment (AT&T Office@Hand, Verizon) — the same paths on
  `https://platform.ringcentral.biz/` (§7).
- The **Interactive Messages shared secret**, if you enabled that feature — "a string you should consider highly
  confidential," used to verify inbound event authenticity.

**Token lifetimes and rotation, stated once.** Access token about an hour (`expires_in` 7199 in RingCentral's own
samples — read the field). Refresh token **7 days** (`refresh_token_expires_in` 604799). And the rules that produce
most "it stopped working" reports:

- **"Refresh tokens can only be used once."** A new refresh token comes back with each refresh and you must store it.
- **"Upon refreshing an access token, the previous access token is invalidated immediately."** Replaying the old
  access token gives HTTP 401.
- There is a narrow grace window, and it is not a feature to rely on: if you *use* the new access token, the old
  refresh token dies in "approximately ten seconds"; if you never use it, the old refresh token stays valid up to
  60 minutes and keeps returning the same access token.
- A connection idle for **seven days** is dead and the customer must re-authorize. This is the single biggest
  behavioural difference from most OAuth platforms and it is worth a scheduled refresh job: RingCentral's own advice
  is a background task that wakes "at least once per day" and refreshes everything stored.

**Other things that silently kill a token**, per the docs: the end user terminates the app's authorization session;
the user's password changes; the company administrator enforces an **"Absolute Session Timeout"**; or "the
application reached the limit (usually five) of parallel active sessions for the same user." That last one bites
multi-instance connectors that authenticate per worker instead of sharing a token.

**Revocation is session-wide.** `/restapi/oauth/revoke` "accepts just one token, [but] it actually terminates the
entire authorization session associated with this token, i.e., invalidates ALL active access and refresh tokens for
this session."

The secret is regenerable from the console. Regenerating invalidates the old value: existing access tokens run out
their hour, but every code exchange and refresh fails until the new value is deployed. Never rotate without explicit
go-ahead and a cutover plan.

Report the secret once so the user can paste it into their secret store, say plainly that it is now in the
transcript and can be regenerated, then move on.

## 7. Limits, review, and listing

**A public app does not need a listing to work.** Public means any RingCentral credential can log in; customers can
authorize through your own connect flow. The App Gallery is *distribution*, not a gate on authorization. That is the
main structural change from the old sandbox model — decide deliberately whether you want the listing at all.

**If you do want the listing**, it goes through review. "Before your app can be listed in an app gallery, the
RingCentral team will first review your app and will make recommendations." The published checklist has three
headings:

1. **Trademarks and intellectual property** — the app name, descriptive text, icons, imagery and media must not
   infringe.
2. **Completeness** — a detailed description covering functionality, target users and value; "at least three
   screenshots showing your product in action, and that highlights specifically how RingCentral is integrated with
   it"; installation, usage and support documentation.
3. **Disclosures and agreements** — "post on your own website a clear privacy policy" and link it from the profile;
   agree to the RingCentral API License Agreement.

The profile itself cannot be submitted until every required field is filled; you can **Save as draft** and come
back. Marketing colleagues can be given Developer Console access to write it (§2). **RingCentral publishes no
turnaround time for App Gallery review** — do not invent one. Compliance attestations and legal commitments are
decisions for a human: collect what exists, do not draft claims.

**Segregated partners are the real multi-host problem.** AT&T Office@Hand and Verizon customers "exist in a
completely segregated environment in order to comply with specific regulations that govern the markets they operate
in":

| Environment | Base API URL | Login host |
| --- | --- | --- |
| Commercial | `https://platform.ringcentral.com/` | `https://login.ringcentral.com` |
| Segregated (AT&T Office@Hand, Verizon) | `https://platform.ringcentral.biz/` | `https://login.ringcentral.biz` |

RingCentral's guidance: detect the partner before you build the login URL, pass the partner's brand ID, "persist
whether their users are associated with RingCentral or a segregated partner, as all subsequent API requests need to
be directed to the proper server environment," and use partner-specific login buttons because "many customers are
not always cognizant of the fact that RingCentral is powering their carrier cloud communication." Other white-label
partners — Avaya Cloud Office, BT, TELUS, Atos Unify Office, Rainbow Office — are on the **commercial** host and need
only the right `brand_id` for branding. (The docs spell that parameter `brandId` in prose and `brand_id` in the
example URLs and the parameter table; send `brand_id` and verify.)

**Rate limits are per (user, app) and per API group, not global.** Defaults, which "can be customized and vary among
apps":

| Usage plan group | Default limit | Penalty interval |
| --- | --- | --- |
| Light | 50 requests/user/minute | 60s |
| Medium | 40 requests/user/minute | 60s |
| Heavy | 10 requests/user/minute | 60s |
| Auth | 5 requests/user/minute | 60s |

Each endpoint's group is named in the API Reference under "Usage Plan Group". Your app's actual numbers are in the
Developer Console under **Rate Limits**, and developer support can raise them per app.

Over-limit is **HTTP 429** with `Retry-After` in seconds ("if `Retry-After` is not returned, the original request
should be retried in 30 seconds or later"), plus `X-Rate-Limit-Group`, `X-Rate-Limit-Limit`,
`X-Rate-Limit-Remaining` and `X-Rate-Limit-Window`. Note the header prefix is **`X-Rate-Limit-…`**, not
`Rate-Limit-…`. Error codes in the 429 body are `CMN-301`/`CMN-302` (request rate exceeded), `CMN-303`, `CMN-304`
(duplicate concurrent request) and `CMN-310` (global).

**The 429 trap is the part that actually hurts a backfill**, and it is unusual enough to design for:

> "Every time you send a request that is caught by our rate limiting system, we reset the clock on your penalty
> window. Therefore, it is possible that your app could find itself trapped in an unending 429 trap, unless you code
> your app such that it allows the penalty window to fully elapse before sending another request."

A naive retry loop does not recover here — it extends the penalty indefinitely. Watch `X-Rate-Limit-Remaining` and
stop *before* the limit; RingCentral's own claim is that an app that does so "should never encounter 429 errors due
to violating user-level rate limits."

**Auth is its own rate-limit group.** "Backend servers enforce some quotas for the number of authorization requests
and number of active application sessions. If the quota is exceeded at any given time, the server starts to return
HTTP 429 on authorization requests." Authenticate in one thread and share the token; do not refresh per worker.

## 8. Verify end-to-end

Authorizing on the account that owns the app proves very little, especially now that that account is production.
Test the path a customer takes:

1. Authorize from a **second RingCentral account** through your platform's real connect flow, with the role
   customers will use — an administrator if the connector reads `account/~` data, a plain extension if not.
2. Confirm the token response actually contains a **`refresh_token`**. If it does not, the "Issue refresh tokens"
   setting is off (§3) — this is the most common single failure on this platform.
3. Force a **refresh**, then force a **second** refresh using the token returned by the first. Refresh tokens are
   single-use; this is what catches a client that stores the original, and it is the highest-value check here.
4. Confirm the **old access token now 401s** — it is invalidated immediately on refresh, not at expiry.
5. Make one real read call per permission you requested, including at least one **account-level** (`account/~`) call,
   which is where an insufficiently privileged installer shows up.
6. If recordings are in scope, download actual **recording content**, not just metadata — they need different
   permissions (§5).
7. Inspect the `scope` string in the token response and confirm it matches what you configured.
8. Watch for `X-Rate-Limit-Remaining` on a real list call, and confirm your client backs off on the whole penalty
   window rather than retrying into it (§7).

| Symptom | Cause |
| --- | --- |
| `OAU-109 Redirect URIs do not match` (403) | `redirect_uri` is not byte-identical to a registered one (§4) |
| `OAU-113 No redirect uri is registered for the client` | App was saved without any Redirect URI (§4) |
| HTTP 400 on `/restapi/oauth/authorize` | Invalid `redirect_uri` in the request (§4) |
| `OAU-110 Authorization code was not issued for this application` | Code exchanged with a different client ID than the one that authorized (§6) |
| `OAU-108 Authorization code is expired` | Code not redeemed in time — the redirect carries `expires_in=60` (§8) |
| `OAU-146 Invalid client credentials` (401) | Wrong client ID/secret, or the secret was regenerated (§6) |
| `OAU-116 Invalid authorization method` / `OAU-123 Invalid Authorization header value` | Platform Type mismatch: Basic auth sent by a client-side app, or omitted by a server-side one (§3) |
| `OAU-125 Grant type is not allowed for application` | `authorization_code` not enabled — app is configured for JWT only (§3) |
| `OAU-112 The client is unauthorized for the required grant type` | Same, seen at the token endpoint (§3) |
| No `refresh_token` in the token response | "Issue refresh tokens" is off; it is **off by default** (§3) |
| Second refresh fails, first one worked | Client replayed the original refresh token — they are single-use (§6) |
| `OAU-128 Access token expired` right after a successful refresh | Old access token reused; refresh invalidates it immediately (§6) |
| Connection dies after a quiet week | 7-day refresh token expiry — the customer must re-authorize (§6) |
| Tokens die when the customer changes their password, or at a fixed hour | Password change invalidates sessions; admin "Absolute Session Timeout" (§6) |
| Tokens drop under load with several workers | Parallel-session cap, "usually five", per user (§6) |
| 403 `InsufficientPermissions` / `CMN-401`, `permissionName` names a scope | That permission is not on the app — add it, and re-check whether it "requires permission" (§5) |
| Call log works, recording download 403s | `ReadCallRecording` missing; content needs it and `ReadCallLog` does not cover it (§5) |
| Auth succeeds, `account/~` endpoints 403 `CMN-402 Administrator permission required` | Installer's RingCentral role lacks admin; app scope alone is not enough (§5) |
| `CMN-404 Attempt to access another extension` | Reaching another user's data without the role that permits it (§5) |
| Login fails for every external customer | App was created **private**; Application Type cannot be edited (§3) |
| Works for most customers, 404/401 for AT&T Office@Hand or Verizon | Those accounts live on `platform.ringcentral.biz` (§7) |
| 429 that never clears despite retrying | Each retry resets the penalty window — let it elapse fully (§7) |
| 429 on `/restapi/oauth/token` specifically | `Auth` is its own group, default 5/user/minute; stop refreshing per worker (§7) |
| A doc URL returns 200 but shows the docs homepage | That page has been deleted — the SPA falls back (§0) |

## Product fact — how the RingCentral connector is configured

> **As of 2026-09-20, Unified.to's RingCentral connector is configured as follows.**
>
> **Endpoints.** It targets the **commercial** environment: `https://platform.ringcentral.com` with
> `/restapi/oauth/authorize` and `/restapi/oauth/token`. It also still carries a second, "Sandbox" environment
> pointing at `platform.devtest.ringcentral.com` — **that host no longer resolves** (§0), so any connection created
> against it cannot work. Raise this with the connector's owner.
>
> **Authorize request.** It sends `client_id`, `redirect_uri`, `response_type` and `state` only. **No PKCE**
> (`code_challenge` is not sent) and **no `scope`** — correct on both counts for this platform, since RingCentral's
> authorize endpoint has no `scope` parameter and PKCE is optional for a confidential client. It follows that the
> app's configured permission list is exactly the consent screen (§5).
>
> **Token exchange.** Form-encoded POST with `grant_type`, `code` and `redirect_uri`, authenticating with **HTTP
> Basic `client_id:client_secret`** — the server-side/confidential pattern (§3). Refresh sends `grant_type`,
> `refresh_token` and `client_id` in the body *and* Basic auth; per the docs, `client_id` in the body is only
> required for a client-side web app, so confirm which Platform Type the app is actually registered as.
>
> **Permissions it expects**, by capability:
>
> | Capability | Permissions requested |
> | --- | --- |
> | Calls / call log | `ReadCallLog` |
> | Call recordings | `ReadCallLog` |
> | Contacts | `ReadContacts` (read), `Contacts` (write) |
> | Directory, devices, sites | `ReadAccounts` (read), `EditAccounts` (site write) |
> | Team Messaging channels, messages, teams | `TeamMessaging` |
> | Meetings / calendar | `Video` |
>
> Three things to raise with the connector's owner before configuring an app:
>
> 1. **Recordings are under-scoped.** The connector downloads recording audio from
>    `…/account/~/recording/{id}/content`, which RingCentral's own access-control table says needs
>    **`ReadCallRecording`** and explicitly does *not* accept `ReadCallLog`. As configured, listing calls will work
>    and every audio download will 403 with `CMN-401`. Add `ReadCallRecording` (§5).
> 2. **Everything is account-level.** The call log, call-log sync and directory calls all use `account/~`, so the
>    authorizing user must be a RingCentral **administrator**. There is no user-level fallback, and a non-admin
>    install authorizes cleanly and then 403s with `CMN-402` (§5).
> 3. **No segregated-environment support.** Only the `.com` host is configured, so AT&T Office@Hand and Verizon
>    customers cannot be served (§7).
>
> It uses **polling** for change detection rather than RingCentral webhook subscriptions, so it needs no
> `SubscriptionWebhook` permission. It also performs a post-authorization profile lookup against
> `/restapi/v1.0/account/~`, whose documented response is account information and carries no verified-email field —
> so that step is unlikely to resolve a user identity. Treat it as suspect until reviewed.
>
> Permission sets change. This note is dated, not live; confirm with the connector's owner before submitting
> anything.

## 9. Hand off — never commit the secret

- **Do not** write the client secret (or an Interactive Messages shared secret) into source control, a test, a
  fixture, a committed `.env`, a ticket, a PR body, or a chat channel. Values go to the user, for the secret store
  or console.
- If a code change is needed (a Redirect URI, a permission, single-use refresh-token handling, the `.biz` host),
  keep it secret-free and say what the human must set out of band.
- Close with: app name and Display name; app type (REST API App) and **Application Type (public/private, permanent)**;
  Platform Type; which RingCentral account owns it; the client ID; where the secret was delivered; the authorize,
  token and revoke endpoints; the exact permission strings and which ones filed an approval ticket; whether "Issue
  refresh tokens" is on; the 7-day single-use refresh-token handling; whether segregated partners are supported; the
  App Gallery decision; and anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when:

- **The client does not store rotated refresh tokens** — report it; the app will register fine and fail on the
  second refresh, and every connection will die within seven days.
- Nobody has decided **public vs private**, or someone proposes "we'll change it later" — you cannot (§3).
- Nobody has provisioned an account to test against. Sandbox is gone; this means a paid Development environment or
  testing against live production data, and that is a decision with a cost and a blast radius (§0, §2).
- A permission marked **"requires permission"** or **"available on request"** is needed and nobody has filed or
  chased the ticket — RingCentral publishes no turnaround for these, so it cannot be scheduled around (§5).
- The product needs **account-wide** data but the install story is individual users self-connecting — that needs an
  administrator, and no app setting substitutes (§5).
- **Call recordings** are in scope and nobody has established who is accountable for the legality of storing call
  audio and its retention. Recording law is jurisdictional; it is not a question the API docs settle.
- **AT&T Office@Hand or Verizon** customers are in scope and the platform has no per-connection API host — that is a
  code change, not a console setting (§7).
- Someone proposes **rotating the client secret** on an app with live customers without a cutover plan (§6).
- Someone asks you to promise an **App Gallery review turnaround**, or to draft compliance, security or legal claims
  for a listing (§7).
- The Developer Console does not match the **Platform state** section above — in particular, if it still offers a
  Sandbox/Production toggle (§0).

## References

Official RingCentral pages only. Every URL verified on 2026-09-20 to return HTTP 200 **and** to render its own
content — this documentation site is a single-page app that returns 200 with the docs homepage for deleted paths, so
a status code alone proves nothing.

- Developer Console — https://developers.ringcentral.com/console
- Sign in — https://developers.ringcentral.com/login
- Developer pricing: Development environment vs Production environment — https://developers.ringcentral.com/pricing
- Registering an application and obtaining app credentials — https://developers.ringcentral.com/guide/getting-started/register-app
- Building your first app (Application Type, Platform Type) — https://developers.ringcentral.com/guide/basics/your-first-steps
- Collaborating with others in Developer Console (roles, Developer Portal Access) — https://developers.ringcentral.com/guide/basics/inviting-developers
- Application permissions and scopes — https://developers.ringcentral.com/guide/basics/permissions
- Choosing the best auth method for your app — https://developers.ringcentral.com/guide/authentication
- Generating an authorization request (parameters, environment table) — https://developers.ringcentral.com/guide/authentication/oauth-request
- Authorization code flow — https://developers.ringcentral.com/guide/authentication/auth-code-flow
- Authorization code flow with PKCE — https://developers.ringcentral.com/guide/authentication/auth-code-pkce-flow
- Using refresh tokens (single use, 7 days, "Issue refresh tokens") — https://developers.ringcentral.com/guide/authentication/refresh-tokens
- Using access tokens (invalidation, revocation, session limits) — https://developers.ringcentral.com/guide/authentication/tokens
- Call Log access control (`ReadCallLog` vs `ReadCallRecording` table) — https://developers.ringcentral.com/guide/voice/call-log/access
- Rate limits (groups, `X-Rate-Limit-*` headers, the penalty-window trap) — https://developers.ringcentral.com/guide/basics/rate-limits
- API error codes (OAU-*, CMN-*, SUB-*, rate-limit codes) — https://developers.ringcentral.com/guide/basics/errors
- Methods, endpoints and parameters (production host, `~` shorthand) — https://developers.ringcentral.com/guide/basics/uris
- Technical requirements for segregated partner environments (`platform.ringcentral.biz`) — https://developers.ringcentral.com/guide/basics/partners/segregated-environments
- Authentication within RingCentral's partner ecosystem (brand IDs, login hosts) — https://developers.ringcentral.com/guide/basics/partners/auth
- Partner and service-provider brand guidelines — https://developers.ringcentral.com/guide/basics/partners/brand-guidelines
- Promoting your application in the RingCentral App Gallery — https://developers.ringcentral.com/guide/getting-started/promote-app
- App Gallery checklist and best practices — https://developers.ringcentral.com/guide/basics/app-gallery-checklist
- API Reference (per-endpoint permissions and Usage Plan Group) — https://developers.ringcentral.com/api-reference
- API changelog — https://developers.ringcentral.com/guide/basics/changelog
