RingCentral Integration
This integration pulls RingCentral REST API v1.0 data into Resplendent Data for call reporting and CS dashboards.
The integration never creates, updates, or deletes records in RingCentral. It only POSTs to the token endpoint. Sending SMS, placing calls, downloading recording audio, live presence webhooks, and Analytics as a separate product are out of this connector.
Before you begin
Section titled “Before you begin”You need credentials from your RingCentral Developer Console. Resplendent Data does not register a vendor app for you.
- A private REST API App with JWT authentication
- The app Client ID and Client Secret
- A JWT credential for a Super Admin or dedicated service user in the same RingCentral org
- The API environment: Production (
https://platform.ringcentral.com) or Sandbox (https://platform.devtest.ringcentral.com). Default is Production.
Treat Client ID, Client Secret, and JWT like passwords. Share them through Nordpass or a similar vault, not email.
Suggested read-only app scopes: ReadCallLog, ReadAccounts / ReadAccountInfo, ReadMessages, and ReadPresence for User Presence and Queue Member Presence. Queue discovery and Queue Members require ReadAccounts. Do not grant send-SMS, call-control, or write scopes.
Company call log requires the JWT user to have Super Admin (or Read Account call log). Extension mailboxes and account info still need the matching read scopes.
User Presence also requires the JWT user to have ReadPresenceStatus and access to the employees’ presence. Existing apps must add ReadPresence before syncing this Dataset; Save & Test checks account info and call logs, not presence access.
What you can sync
Section titled “What you can sync”| Dataset | Sync behavior | Notes |
|---|---|---|
| Call Logs | Incremental; first batch then page history | Company call log GET /restapi/v1.0/account/~/call-log. Watermark is startTime. Nightly resync recovers deletes |
| Extensions | Snapshot every 1440 minutes by default | GET /restapi/v1.0/account/~/extension. Full list in one job |
| Messages | Incremental; first batch then mailbox crawl | Per-extension message-store (no account-level list). Watermark is creationTime. Needed for SMS/text reporting |
| User Presence | Snapshot every 1 minute by default | GET /restapi/v1.0/account/~/presence. Current phone and availability state per extension |
| Queue Members | Snapshot every 1440 minutes by default; minimum 360 minutes | Lists queues, then all members of each queue |
| Queue Member Presence | Snapshot every 1 minute by default; minimum 1 minute | Lists queues, then reads each member’s queue acceptance flags |
Call Logs are incremental with integer page / perPage paging (ConnectWise style). initial_pull writes one configured batch (batch_pull_size, default 250). Call Logs use view=Detailed so lastModifiedTime is present; RingCentral caps Detailed perPage at 250, and that cap is the Dataset batch size so crawler_step stays one vendor page. Then get_old_rows continues from crawler_step. last_resync_timestamp does not change fetch size. History uses dateFrom=2010-01-01T00:00:00.000Z so RingCentral does not default to the last 24 hours.
Messages are incremental with a JSON mailbox cursor in crawler_step_info (extension_id + page). There is no account-wide message-store; the job lists User, Department, and Voicemail extensions, then pages each mailbox. Incremental updates use dateFrom from the warehouse watermark plus a two-minute overlap.
Extensions are snapshots. initial_pull returns the current list. Later scheduled ticks replace parquet. get_old_rows is unused.
User Presence is also a snapshot, with a default and minimum sync interval of 1 minute. Each sync reads all presence pages and replaces the previous snapshot. Removed extensions disappear on the next successful sync. It does not retain status history or capture calls that begin and end between polls. The interval is a scheduling setting, not a maximum age guarantee: dispatcher timing, queue processing, API throttling, and dashboard refresh can add delay. Display observed_at alongside status so viewers can see when the data was last observed.
Queue Members and Queue Member Presence are complete snapshots. Each pull discovers every queue using GET /restapi/v1.0/account/~/call-queues, then reads /call-queues/{queueId}/members or /call-queues/{queueId}/presence. All pages are included. Queue names and numbers are included in each row, so a separate Call Queues Dataset is not required. Queue Member Presence discovers the current queue list on every poll, independently of the slower Queue Members refresh.
Queue polling needs at least one list request plus one request per queue, with additional requests for pagination. Large accounts or shared API traffic may require a slower presence interval. Both Datasets use the existing 60-second worker timeout. Failed or incomplete scheduled pulls leave the previous snapshot in place; no partial membership list is published. Successful pulls remove memberships or queues that disappeared. An empty queue produces no membership rows.
Hard deletes on incremental Datasets are recovered by nightly resync (wipe, then the first configured batch, then history recrawl). Snapshot replace already drops extensions that left RingCentral. Call recording metadata is stored as scalars plus recording_json; audio files are not downloaded.
Setup instructions
Section titled “Setup instructions”Step 1: Create a private REST API app
Section titled “Step 1: Create a private REST API app”- Sign in to the RingCentral Developer Console
- Open Apps and create a REST API App
- Choose JWT authentication, private app, and the read-only scopes above
- Copy the Client ID and Client Secret
Step 2: Create a Super Admin JWT
Section titled “Step 2: Create a Super Admin JWT”- Open the name menu → Credentials
- Create a JWT for a Super Admin or dedicated service user in the same org
- Restrict the JWT to the app you just created
- Copy the JWT once; it cannot be shown again
Step 3: Connect in Resplendent Data
Section titled “Step 3: Connect in Resplendent Data ”- Go to Settings → Integrations
- Find RingCentral and click Connect
- Choose Production or Sandbox
- Paste the Client ID
- Paste the Client Secret and JWT credential
- Click Save & Test
- After a successful test, click Sync Dataset and choose the Datasets to sync
Save & Test exchanges the JWT for an access token (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer), reads account info, then probes one company call-log row so a missing Super Admin permission fails immediately.
Limits and behavior
Section titled “Limits and behavior”- Auth is the official JWT grant. Access tokens are reused until near expiry, refreshed with
refresh_tokenwhen valid, and only re-exchanged with the JWT when both access and refresh are dead. RingCentral auth is rate-limited (about 5 token calls per minute). - Call Logs paging is
page(1-based) andperPagefrom 1 to 250 (view=Detailed). Messages and Extensions still page up to 1000. - Messages crawl one mailbox page at a time across extensions. Large orgs recrawl one batch per minute until history is done.
- HTTP 429 responses are retried using
Retry-AfterorRateLimit-Reset. - Nested objects are expanded into columns at ingest with Polars
json_normalize. The original objects stay in*_jsoncolumns so no payload is dropped. Call-logfrom/toare objects, not arrays. Message-storetoandattachmentsstay arrays in JSON (one row per message). Call-loglegsstay inlegs_json(not unnested into extra rows).deviceinside caller info stays a JSON string (from_device/to_device), not exploded.profileImagestays nested JSON.
Dataset columns
Section titled “Dataset columns”Call Logs
Section titled “Call Logs”Stable Detailed-view fields: id, uri, session_id, telephony_session_id, start_time, duration, type, internal_type, direction, action, result, transport, last_modified_time.
Promoted from from / to (CallLogCallerInfo objects): from_phone_number, from_name, from_extension_number, from_location, from_extension_id, from_device, to_phone_number, to_name, to_extension_number, to_location, to_extension_id, to_device.
Promoted from recording when present: recording_id, recording_uri, recording_type, recording_content_uri, recording_duration.
Promoted from the voicemail-link message object when present: message_id, message_type, message_uri.
Promoted from billing: billing_cost_included, billing_cost_purchased.
Promoted from extension: extension_id, extension_uri.
JSON blobs that remain: from_json, to_json (includes nested device), recording_json, message_json, billing_json, extension_json, legs_json (array of legs, not unnested).
Messages
Section titled “Messages”Stable fields: id, extension_id, uri, conversation_id, type, direction, availability, read_status, priority, message_status, subject, creation_time, last_modified_time, sms_delivery_time.
Promoted from from (object): from_phone_number, from_name, from_extension_number.
Promoted from to (array, comma-joined, not unnested): to_names, to_phone_numbers.
Promoted from attachments (array, not unnested): attachment_count.
JSON blobs that remain: from_json, to_json, attachments_json.
Extensions
Section titled “Extensions”Stable fields: id, uri, extension_number, name, type, status, hidden, creation_time.
Promoted from contact: email, first_name, last_name, department, company, job_title, business_phone, mobile_phone.
Promoted from permissions: admin_enabled, international_calling_enabled.
JSON blobs that remain: contact_json, permissions_json, profile_image_json (low dashboard value, left nested).
User Presence
Section titled “User Presence”Fields: extension_id (primary key), extension_number, telephony_status, presence_status, user_status, dnd_status, allow_see_my_presence, and observed_at (UTC time Resplendent processed the observation, not the time the phone status changed).
Join extension_id to the Extensions Dataset’s id for employee names and departments, then filter to the support team.
| Phone status | Meaning |
|---|---|
NoCall |
Not currently on a call |
CallConnected |
Connected to a call |
Ringing |
Phone is ringing |
OnHold |
Call is on hold |
ParkedCall |
Call is parked |
| Empty | Phone status was not provided; treat as unknown |
Availability and Do Not Disturb are separate from phone status. NoCall does not mean an employee is available to take a call. Some non-user extension types return Offline without a phone status. Missing phone status is never converted to NoCall.
Polling uses the account presence endpoint instead of one request per employee. RingCentral counts it in the Heavy rate-limit group; actual limits depend on the app and other API usage. Failed or incomplete scheduled pulls do not publish a replacement snapshot.
Queue Members
Section titled “Queue Members”Fields: queue_member_id (primary key, queue_id:extension_id), queue_id, queue_name, queue_extension_number, extension_id, extension_number, member_name, and observed_at (UTC observation time).
Each row represents one employee in one queue. Employees in multiple queues have a separate row for each membership. Join extension_id to Extensions id or User Presence extension_id for employee details and phone status. Member names may be absent from RingCentral’s membership response; the Extensions Dataset supplies those names.
Queue Member Presence
Section titled “Queue Member Presence”Includes all Queue Members columns plus accept_queue_calls and accept_current_queue_calls. The first flag describes whether the employee accepts calls through queues generally; the second describes acceptance for this particular queue. An empty flag means unknown, not false. The flags do not indicate whether the employee is on a call; join to User Presence for that status.
Join queue_member_id between the two queue Datasets when comparing membership and acceptance. These are current snapshots, not historical availability or staffing schedules.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | What to try |
|---|---|---|
| invalid_client | Wrong Client ID or Client Secret, or the JWT is not restricted to that app | Recopy Client ID/Secret from the same app the JWT is bound to |
| Expired or revoked JWT | JWT was rotated, expired, or the user was disabled | Create a new JWT for the Super Admin / service user and paste it |
| Account call log access denied | Token works but company call log returned HTTP 403 | Use a Super Admin JWT, or grant Read Account call log |
| User Presence access denied | App or JWT user lacks presence access | Add ReadPresence to the app and ReadPresenceStatus to the user’s permissions; refresh the connection and retry |
| Queue Members or queue discovery access denied | App or JWT user cannot read queues | Grant ReadAccounts and access to the account’s call queues |
| Queue Member Presence access denied | Queue discovery works but presence access is missing | Grant ReadPresence and the corresponding user access, then retry |
| Throttling / HTTP 429 | RingCentral rate limit, including token-endpoint 5/min | Wait and retry; do not hammer Save & Test; reduce concurrent API consumers |
| Incomplete history after save | Incremental Datasets recrawl one batch per minute | Wait for get_old_rows to finish, or raise the Dataset history setting |
| Missing expanded call-log columns | Dataset columns were saved from an older preview, or parquet was merged before this ingest change | Resync Call Logs and re-select columns if the Dataset is pinned to an old list |
last_modified_time is empty |
Call Logs were synced with Simple view, which omits lastModifiedTime |
Resync Call Logs; this connector now requests view=Detailed |
| Missing deleted calls or texts | Nightly resync has not run since the delete | Wait for nightly resync, or resync the Dataset |
| Sandbox host errors | Production credentials used against sandbox, or the reverse | Match Environment to the Developer Console app |