Pull declined/deferred service data (recommendations customers didn't buy) for any Tekion store via the internal Deferred Services report API — service name, counts, dollar estimates, customer/vehicle/advisor detail. Zero OpenAPI quota; works when OVERALL_QUOTA is exhausted. Use for marketing campaign lists (Autumn) or lost-sales analysis.
Install
npx skillscat add joecastelino/jay-skill-pack/tekion-deferred-services-report Install via the SkillsCat registry.
Tekion Deferred/Declined Services Report
Proven end-to-end at BC (dealer 1251) 2026-07-08: 2,915 deferred line items over 90 days,
harvested in ~15 pages via the :9223 authenticated browser. Zero OpenAPI quota.
Key facts
- Tekion's UI report = REPORTS module →
/core/reports/service/deferredServices. - Backend =
POST /api/service-module/u/reporting/recommendation/search(internal API,
fire in-page via fetch with capturedwindow.__Hheaders + credentials:'include'). - Status vocabulary: recommendations are DEFERRED (declined) or APPROVED (sold).
DECLINED / RECOMMENDED / OPEN / COMPLETED all return 0 — "declined" == DEFERRED. - The UI's default filter adds
comebackCompleted NIN [true](excludes ones the customer
later came back and bought). Include or drop per the ask; counts differ (~204 vs 2,915
difference at BC was mostly the roClosedTime default window, not comeback).
Request body (paginates properly, unlike activity-log)
{"reportName":"RO_RECOMMENDATIONS","reportGroup":"RECOMMENDATION",
"sort":[{"field":"universalId","order":"ASC"}],
"filters":[
{"operator":"BTW","values":[t0_ms,t1_ms],"type":"roClosedTime","key":"roClosedTime","field":"roClosedTime"},
{"field":"status","values":["DEFERRED"],"operator":"IN"}],
"pageInfo":{"start":0,"rows":200}}Response: data.reportData.count + data.reportData.hits[]. Paginate bystart += hits.length until start >= count — pageInfo pagination WORKS here (200/page,
15 pages for 2,915, ~300ms delay between pages was fine).
Hit record fields (the useful ones)
concern= the service name/description (free-text advisor concern — normalize!)opcodes= array (often ["MISC"] or ["REC"], not a real opcode — concern is the name)jobAmounts.estimateAmount= declined-job estimate in CENTS (/100).totalAmountalso cents.roNo,roId,roClosedTime,payTypeprimaryAdvisorId(UUID — resolve via OpenAPI /users/{id} when quota returns)customer= {name, firstName, lastName, email, phone} — full contact info for campaign listsvehicle= {vin, year, makeLabel, model, trim, mileageIn}severity,mpvi,comebackCompleted,lastDeferredBy/Time,approvedOrDeferredDetails= {comment, communicationMode, communicatedDate}
Recipe
- :9223 alive + correct dealer: check
localStorage.currentActiveDealerId; switch via the
dealer pill UI (mouse click x≈1130,y32 → click store row center; localStorage set does NOT work). - Capture headers: arm a setRequestHeader hook filtered to
recommendation/search, then
SPA-navigate (history.pushState + PopStateEvent) away and back to/core/reports/service/deferredServicesto force the app to fire the XHR →window.__H. - Harvest in-page: async loop with the body above, accumulate
window.__DEF[h.id]={...trimmed fields}. - Ferry out:
window.__S=JSON.stringify(window.__DEF), pull in 15,000-char slices via /eval
(eval truncates ~20k), reassemble in Python. Use hermes_tools json_parse for curl output. - Aggregate: normalize
concern(strip RECOMMENDATION:/REC: prefixes, collapse whitespace,
uppercase) — free text means near-dupes remain ("WHEEL ALIGNMENT" vs "4 WHEEL ALIGNMENT");
note that to the requester. Group → count, avg $ (estimateAmount/100), total $. - Output CSV to /home/itadmin/tekion-reports/data/ (persistent — NOT ~ which is ephemeral).
Pitfalls
- estimateAmount is CENTS (standard Tekion) — /100 or you'll report $1M declines.
- Some records have blank concern → "(NO DESCRIPTION)" bucket; exclude from top-N, mention count.
- OpenAPI has NO recommendations endpoint — this internal API is the only programmatic path.
- Fresh page loads wipe window hooks — use pushState SPA nav, not location.href.
- Reference run files: /home/itadmin/tekion-reports/data/bc-deferred-90d-raw-2026-07-08.json,
BC-declined-services-top20-90d-2026-07-08.csv.