02 Get your data in /Integrations
Oracle B2C Service
Send Oracle B2C Service incidents and their threads to Dimension Labs with the universal API.
- OB Oracle B2C ServiceTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in each Oracle B2C Service (formerly Service Cloud and RightNow) incident as one conversation: the customer’s entries and your staff’s responses, across email, chat, web forms and the other channels your site uses. You run it on your side: a small script pulls from the Connect REST API and posts each message to the universal API.
What you need
Section titled “What you need”- Your site’s REST server URL, for example
https://mysite.example.com. Oracle sends it to your site administrator in the welcome email. - A staff account whose profile has permission to read incidents through the REST API, and its user name and password.
- A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Oracle B2C Service
Section titled “1. Get API access in Oracle B2C Service”- Ask your site administrator for the REST server URL and a staff account for the integration. Its profile needs access to the incidents resource (quick start).
- Authenticate with Basic auth: the staff account’s user name and password. Session tokens and OAuth Bearer tokens from an external identity provider also work (authenticate and authorize).
- Send the
OSvC-CREST-Application-Contextheader on every request, with a short label such asDimension Labs sync. From version 1.4 it is required: without it, every request fails with400 Bad Request. It can be at most 40 characters (custom headers).
Your administrator can map that label to a resource pool, which caps how many requests it can run at once (API throttling).
2. Pull new incidents
Section titled “2. Pull new incidents”Get incidents ordered by when they last changed, newest first, and stop once you reach incidents older than your cursor:
curl -u "$OSVC_USERNAME:$OSVC_PASSWORD" \ -H "OSvC-CREST-Application-Context: Dimension Labs sync" \ "https://mysite.example.com/services/rest/connect/v1.4/incidents?orderBy=updatedTime:desc&limit=100&offset=0"Each item has id, lookupName (the reference number), createdTime and updatedTime. Page with limit and offset while the response has hasMore: true.
For each incident, get the incident with ?expand=threads. A plain GET doesn’t include threads; with expand=threads, the threads.items list holds a link to each thread, such as /incidents/35/threads/24. Get each thread for its text, entryType, contentType, contact, account and createdTime (supported HTTP methods).
Oracle doesn’t publish a fixed rate limit. Each response carries two headers: osvc-crest-next-request-after, the milliseconds to wait before your next request, and osvc-crest-api-access-token, which you pass back unchanged on the next request. Leaving the token out can degrade performance and cause errors. A request over your resource pool’s limit returns 503.
Oracle says not to build integrations from the swagger.json on its help center, which is a sample for documentation only. Work from the reference pages.
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | Oracle B2C Service | Notes |
|---|---|---|
text | thread text | Strip HTML when contentType is HTML. Skip private notes (see incoming). |
userId | incident primaryContact.id | The contact’s internal id, on both their entries and your responses. Fall back to the sessionId. |
incoming | thread entryType.lookupName | Oracle’s entry types are Customer Entry, Response and Private Note. Customer entries are incoming, responses outgoing, and private notes are staff-only, so skip them. The code keeps only names with “customer” or “response”. Check the names on your site with one thread GET before the history load. |
sessionId | incident id | Prefix it, for example oracle-b2c-service-67. |
conversationId | incident id | Same value as sessionId. |
dimensionlabs_timestamp | thread createdTime | Convert with toMillis. Threads with the same time are ordered by displayOrder and get 1,000 ms added each. |
platformJson | incident lookupName, statusWithType, channel, source; thread channel | The thread’s channel is the channel of that entry. |
Personal data. Don’t send the contact’s name or email, the thread’s mailHeader, or the staff account’s name. Threads can hold addresses, phone numbers and account numbers: redact them before you send. See PII redaction.
4. Send it
Section titled “4. Send it”// oracle-b2c-service.mjs. Environment: OSVC_SITE (https://mysite.example.com), OSVC_USERNAME, OSVC_PASSWORD, DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const base = `${process.env.OSVC_SITE}/services/rest/connect/v1.4`;const auth = 'Basic ' + Buffer.from(`${process.env.OSVC_USERNAME}:${process.env.OSVC_PASSWORD}`).toString('base64');const stripHtml = (html) => String(html ?? '').replace(/<br\s*\/?>|<\/p>/gi, '\n').replace(/<[^>]+>/g, ' ') .replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&') .replace(/[ \t]+/g, ' ').replace(/\s*\n\s*/g, '\n').trim();
let accessToken = null; // osvc-crest-api-access-token, passed from each response to the next requestlet waitMs = 0; // osvc-crest-next-request-after
// Honors Oracle's pacing headers, and retries rate limits (429) and server errors (5xx, including 503 Server Busy).async function get(url) { for (let attempt = 0; ; attempt++) { await sleep(waitMs); const headers = { Authorization: auth, 'OSvC-CREST-Application-Context': 'Dimension Labs sync' }; if (accessToken) headers['osvc-crest-api-access-token'] = accessToken; const res = await fetch(url, { headers }); accessToken = res.headers.get('osvc-crest-api-access-token') ?? accessToken; waitMs = Number(res.headers.get('osvc-crest-next-request-after')) || 0; if (res.ok) return res.json(); if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Oracle B2C Service returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000); }}
// Incidents changed at or after `since`, newest first.async function changedIncidents(since) { const found = []; for (let offset = 0; ; offset += 100) { const page = await get(`${base}/incidents?orderBy=updatedTime:desc&limit=100&offset=${offset}`); const fresh = page.items.filter((i) => toMillis(i.updatedTime) >= since); found.push(...fresh); if (!page.hasMore || fresh.length < page.items.length) return found; }}
const kind = (t) => { const name = t.entryType?.lookupName ?? ''; if (/private/i.test(name)) return null; if (/customer/i.test(name)) return 'in'; if (/response/i.test(name)) return 'out'; return null;};
// Sends every customer entry and response written at or after `since` (Unix ms). Returns the new `since`.export async function syncOracleB2cService({ since, historical = false }) { const started = Date.now(); let newest = since; for (const { id } of await changedIncidents(since)) { const incident = await get(`${base}/incidents/${id}?expand=threads`); const threads = []; for (const link of incident.threads?.items ?? []) threads.push(await get(link.href)); threads.sort((a, b) => toMillis(a.createdTime) - toMillis(b.createdTime) || a.displayOrder - b.displayOrder); let last = 0; for (const t of threads) { const written = toMillis(t.createdTime); const ts = written <= last ? last + 1000 : written; last = ts; const direction = kind(t); const text = /html/i.test(t.contentType?.lookupName ?? '') ? stripHtml(t.text) : String(t.text ?? '').trim(); if (!direction || written < since || !text) continue; await sendToDimensionLabs({ text, userId: incident.primaryContact?.id ? String(incident.primaryContact.id) : `oracle-b2c-service-${id}`, incoming: direction === 'in', sessionId: `oracle-b2c-service-${id}`, conversationId: `oracle-b2c-service-${id}`, dimensionlabs_timestamp: ts, platformJson: { reference: incident.lookupName, status: incident.statusWithType?.status?.lookupName, incident_channel: incident.channel?.lookupName, source: incident.source?.lookupName, channel: t.channel?.lookupName, entry_type: t.entryType?.lookupName, }, }, { historical: historical || ts < Date.now() - HOUR }); newest = Math.max(newest, written); } } // A record updated mid-run can shift the paging; stopping the cursor a little before the run started means the next run reads it. Re-sends are safe. return Math.max(since, Math.min(newest, started - 5 * 60 * 1000));}5. Run it
Section titled “5. Run it”Save the code as oracle-b2c-service.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncOracleB2cService as sync } from './oracle-b2c-service.mjs';.
- History first:
node run.mjs 2025-01-01sends every entry and response since that date to/trackhistorical. Then start a backfill from the Backfill tab. - Then every 15 minutes: the scheduled job sends new entries to
/track, and they are enriched automatically every day. An entry that is more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. - Re-runs are safe: duplicates are removed automatically.
Each incident costs one call plus one per thread, and the code waits as long as osvc-crest-next-request-after asks, so a history load takes a while. Ask your administrator to map the application context to a resource pool if the job competes with other integrations. You can also upload a file of exported incidents with one row per thread (check it with the file checker), then run a backfill.
Oracle B2C Service reference
Section titled “Oracle B2C Service reference”- Quick start
- Authenticate and authorize
- Custom headers
- API throttling
- Supported query parameters
- Supported HTTP methods
- Incidents REST endpoints
- Get multiple incidents
- Get an incident
- Incident thread
Checked against Oracle’s documentation on 2026-10-26.