How to get RO approval information / approval notes (additional warranty hours approvals, warranty job approvals, recommendation approvals) to print on a Tekion RO / invoice PDF. Covers the prerequisite Service Settings + support ticket, the PDF Configurator toggle, and where the live UI differs from the KB article. Use when Joe or a store asks "how do I get the approval notes on the RO PDF" or "approvals aren't printing on the warranty invoice".
Install
npx skillscat add joecastelino/jay-skill-pack/tekion-approval-notes-on-ro-pdf Install via the SkillsCat registry.
Tekion — Approval notes on the RO PDF
Source of truth: KB0010837 (HOW TO: RO Approval Process) + KB0021613
(Service Settings - PDF Settings - PDF Configurator), plus LIVE verification on
SCT (876) and VC (1891) 2026-08-25.
The 3 layers — all must be true
1. Approvers / approval rules must exist
KB0010837 says email support@tekion.com with the login ID(s) of the approvers.
That KB is stale — on the current build there is a self-serve
Approval Setup app at https://app.tekioncloud.com/core/approval-setup
(App Grid → Settings → Approval Setup). It lists Business Processes:Deal recap (Sales) and Service (Service), each with a rule count.
Build the Service approval rules there; only fall back to a support ticket if the
Service business process is missing.
TL (1092) baseline 2026-08-25: Service business process = 2 rules, last modified
that day (Joe built them). Deal recap = 0 rules.
2. Turn the approval workflow on
Service Settings → General Setup → toggle "Enable RO Approval flow".
URL /service/settings/ro-settings.
(The KB calls this section "Approval Settings"; on the current build it's theEnable RO Approval flow toggle in General Setup.)
AMG baseline 2026-08-25: SCT (876) has Enable RO Approval flow = OFF. If it's off,
no approvals are ever recorded, so nothing can print — flipping the PDF toggle alone
does nothing.
3. Turn the print field on in the PDF Configurator
App Grid → Settings → Services Settings → PDF Settings — direct URLhttps://app.tekioncloud.com/ro/pdf-settings
(NOT /service/settings/pdf-settings, which silently redirects to ro-settings).
Click the PDF you want (e.g. Invoice - Warranty Pay) → Body tab.
Two distinct rows live in Body:
| Body row | What it prints | Configurable? |
|---|---|---|
| Approval List | the RO approval records (requested/approved additional hours + warranty jobs, with the notes/comments entered during the approval) | plain on/off toggle, no sub-config |
| Recommendation Approval Details | the recommendation-approval verbiage line | has Configure Section → Approval verbiage text (default "Approved by Service Advisor on behalf of customer") |
Toggle Approval List ON → Generate Preview → Save And Publish.
Which PDFs even have the Approval List row (verified SCT 876 + VC 1891):
- ✅ Invoice - Warranty Pay
- ✅ Invoice - Service Advisor
- ❌ Invoice - Customer Pay, Invoice - Internal Pay, Closed RO Invoice — row does NOT exist.
This matches the KB: "The approval information will display on the warranty invoice PDFs."
AMG baseline 2026-08-25 (Enable RO Approval flow / Approval List on Warranty Pay /
Approval List on Service Advisor):
- SCT 876 — flow OFF / WP OFF / SA OFF
- VC 1891 — flow OFF / WP OFF / SA OFF
- TL 1092 — flow ON ✅ / WP ON ✅ / SA ON ✅ (all three set by Jay 2026-08-25 at Joe's request; SA copy republished 3:12 PM)
Recommendation Approval Details = ON everywhere.
CRITICAL: two different "approvals" — do not confuse them (verified TL 2026-08-25)
Tekion has two unrelated approval systems. Only ONE of them prints its comment.
Approval Workspace (/core/approval-workspace) |
RO Recommendation Approval | |
|---|---|---|
| Module | arcapproval (Core) — rules engine from Approval Setup |
service-module |
| What it is | Rules-engine sign-off: APPR0826-0000NN records, business process = Service, submitter, approvers by level | Advisor recording that the customer approved recommended work |
| Where the comment is typed | Approver's "Please add your comment" box when they Approve (e.g. "SEAN IS RAD!") | RO → Recommendations → approve on behalf of customer → Note field |
| PDF Body row | Approval List (fed by service-module OTHER_LABOR_HOURS requests, not by arcapproval) |
Recommendation Approval Details |
| Does the comment print? | ❌ NO — only Total Approval Request : N prints |
✅ YES — prints as Note<text> |
| How to get the comment out | Export → Excel/CSV (KB0021342) — needs All Request View Approval Workspace |
prints on invoice + R&I PDFs |
Verified on TL RO 398624 (Warranty Pay v2 PDF, all 3 layers ON, 2 completed
Approval Workspace records incl. one with comment "SEAN IS RAD!"):
ApprovalListTotalApprovalRequest:0
...
RecommendationApprovalDetails Sean Preston approved recommendations on behalf of Sean Preston.
ModeOfCommunication InPerson PreviousEstimate $0.00 RevisedEstimate $236.63
DateandTime Tue Aug 25, 2026 at 03:01 PM
Note wARRANTY WORK APRROVAL <-- the ONLY note that printsSo: the Approval List section is a counter, not a notes section, and it read
0 even though Approval Workspace showed completed records — because
Approval-Workspace approvals are a different object than the RO-levelRO Bulk Action → Approval → Additional Hours requests that Approval List counts.
If someone wants an approver's comment (like "SEAN IS RAD!") on the PDF: it is not
available in the standard PDF Configurator. Options: (a) have the advisor type the
text into the Recommendation approval Note field instead — that DOES print;
(b) open a Tekion enhancement request to surface Approval Workspace comments.
Recommendations And Inspection PDFs — the BEST place for approval notes
There are three R&I documents in PDF Settings (all Published at TL):Recommendations And Inspection - Warranty / - Customer / - Internal.
None of them has an Approval List row (that row exists only on Invoice - Warranty
Pay and Invoice - Service Advisor). So Approval Workspace comments do not print here
either. BUT all three have Recommendation Approval Details → Configure Section = ON,
and — critically — the R&I PDF prints EVERY approval event with its Note, while the
invoice prints only ONE (the last).
Verified TL RO 398624, Recommendations and Inspection - Warranty ("Warranty Copy v4",
3 pages) — page 2 carried the full approval audit trail:
Recommendation Approval Details
Sean Preston approved recommendations on behalf of Sean Preston.
Mode Of Communication In Person | Previous $0.00 | Revised $236.63
Date and Time Tue Aug 25, 2026 at 03:01 PM
Note wARRANTY WORK APRROVAL
---
Mode Of Communication In Person | Previous $236.63 | Revised $709.89
Date and Time Tue Aug 25, 2026 at 02:56 PM
Note SEAN APPROVED FOR TESTING PURPOSE
---
Mode Of Communication In Person | Previous $473.26 | Revised $709.89
Date and Time Tue Aug 25, 2026 at 01:06 PM
Note SEAN PRecommendation: if a store wants approval notes on paper, print theRecommendations and Inspection - Warranty copy, not the invoice. It gives the
full chronological trail with dollar deltas (previous → revised estimate),
mode of communication, timestamp, and the free-text Note for each approval.
Body rows on the R&I - Warranty PDF (TL, all ON unless noted): Inspection Details ·
RO External Notes (OFF) · Pending Recommendations (+Summary) · CA Recommendations
(+Summary) · Jobs (Configure Section) · Jobs Recommendations Summary · Deferred
Recommendations · Summary · Signature Placeholder · Recommendation Approval Details
(Configure Section) · Inspection Media · BAR/EPA.
The Customer and Internal variants expose the same recommendation/approval rows.
Gotcha: R&I PDFs render INLINE, not via a signed S3 URL
Unlike the invoice copies, clicking Recommendations and Inspection - Warranty in
View RO PDF does NOT fire /api/exports/pdf-v3 + presignedurls — the XHR hook
captures nothing (window.__pu stays []). The document is rendered straight into
the DOM. Just read it:
const t = document.body.innerText.replace(/\n+/g,' | ');
t.slice(t.indexOf('Warranty Copy v4'), t.indexOf('Warranty Copy v4')+3400)No base64 slicing, no −27 font decode needed. Try this FIRST before the S3 route.
PULLING APPROVAL WORKSPACE COMMENTS PROGRAMMATICALLY (SOLVED 2026-08-25, TL)
This is how you actually retrieve an approver's free-text comment (e.g. "SEAN IS RAD!")
since it does NOT print on any PDF.
Permission prerequisite: the login needs All Request View Approval Workspace
(+ View All Requests <Department>) — Roles → Permissions → Core → Approval Setup.
Without it the workspace dropdown shows only My Approvals | My Requests and both read 0,
because My Approvals = awaiting YOUR approval and My Requests = raised BY you. A record
submitted AND self-approved by another user (Sean is both submitter and a Level-1 approver)
appears in neither. With the permission a third option All Approvals appears in the
dropdown at approximately (164, 274) after clicking the label at (176, 168).
Endpoint: POST /api/arcapproval/u/approval/search?locale=en_US
AUTH TRAP: a bare in-page fetch using localStorage['t_token'] returns500 {"message":"Token doesn't exist or is invalid"} in every header variant. The axios
interceptor injects ~17 headers. Capture them instead — hook XMLHttpRequest.prototype.setRequestHeader,
trigger the app's own search (toggle the dropdown My Approvals → All Approvals), then replay:
const SR=XMLHttpRequest.prototype.setRequestHeader;window.__hdr=null;
XMLHttpRequest.prototype.setRequestHeader=function(k,v){
if(!this.__h)this.__h={};this.__h[k]=v;
if(/approval\/search/.test(this.__u||''))window.__hdr=this.__h;
return SR.apply(this,arguments)};(Requires the open hook first to set this.__u.) Required headers includetekion-api-token, roleId, userId, tenantname, dealerId, tek-siteId,applicationId: ARC_NA, productIds: ARC.
Request body — send EMPTY filters to get everything (the UI always injects astatus IN [PENDING] filter, which hides COMPLETED records — that's why the tab reads 0):
{"sort":[{"field":"createdTime","order":"DESC"}],"filters":[],"searchText":"",
"groupBy":[],"includeFields":[],"searchableFields":[],"excludeFields":[],
"pageInfo":{"start":0,"rows":50}}Response shape: data.count + data.hits[]. Per hit:
requestDisplayName=APPR0826-000012,requestName=Service - <RO#>status= COMPLETED / PENDING / WITHDRAWN / DRAFTnotesis a SINGLE OBJECT, not a list —notes.noteholds the comment text.
Only the LATEST note survives here; there is no note history array in this payload.createdBy= submitter uid,approvalTaskResponses[0].approvers[].approverId= who approved,.eligibleApprovers[]= the rule's approver pool. Resolve uids via OpenAPIGET /users/{id}.- Times are epoch ms → convert to
America/Los_Angeles. - DRAFT rows can carry
note: "Source: RECOMMENDATION 1"— confirms these records originate
from RO recommendations even though they live in a separate service (arcapproval).
Assemble the response out of the browser in ≤16,000-char substr slices (eval truncates ~20,000).
WHERE THE APPROVER COMMENT IS VISIBLE IN THE UI (verified TL 2026-08-25)
It IS on screen, just buried 3 levels deep. Approval Workspace → select the request →
right detail panel → scroll to Rules Matched (N) → click the rule name row
(e.g. "Warranty Recommendation", it's an ant-collapse-header — collapsed by default)
→ expands to reveal Rule Criteria + Approvers:
Approvers
Level 1: Approved
Sean Preston
25th Aug, 2026 | 02:55 PM
SEAN IS RAD! <- the comment, in italicsThe comment is NOT in the collapsed view — the panel's top-level "Comment" field shows
the SUBMITTER's comment (Source: RECOMMENDATION 4), which is a different field entirely.
Easy to mistake one for the other.
TRAP: the list itself reads (0) until you clear the default filter. Filter funnel icon
(~89,262) → filter group shows Status | In | Pending → click the Status value selector
(~593,447) → 7 options (Pending/Completed/Declined/Withdrawn/Expired/Returned/Void) →
multi-select the rest → Apply (~887,626). Then the list populates.
THERE IS NO NATIVE EXPORT / PDF (verified TL 2026-08-25)
Checked exhaustively, all negative:
- No export/download/print icon on the list toolbar OR the detail panel (DOM scan for
export|download|print|pdfin className/data-test-id returns []). - Bulk Actions toggle (~268,262 — the switch itself, not the label) is approve/decline
only, and explicitly states "Only the 'Pending' requests are eligible for bulk actions"
and max 15 at a time. No export action. - Nothing on the RO. The Approval Workspace record is NOT surfaced on the RO detail page,
and there is no approval indicator on the Recommendations tab. - KB0021342 documents an Export, but it does not exist in this build/permission set.
So the answer to "where can I export it as a PDF?" is: you can't — build it.
THE FIX: /home/itadmin/tekion-reports/approval_log_report.py
Generates a proper PDF audit log including the approver comment.
python3 approval_log_report.py --days 7 --out /tmp/tl_approval_log.pdf \
--store "Toyota of Lancaster" --dealer-key tlColumns: Request · RO · Status · Submitted by · Approved by · When · Approver Comment.
Resolves every uid to a real name via OpenAPI GET /users/{id}. Verified 2026-08-25:
14 TL records, "SEAN IS RAD!" correctly attributed to APPR0826-000012.
Upgrades 2026-09-02 (all verified):
- Clickable hyperlinks: RO number → the payload's
transactionIds.valuedeep link
(authoritative — do NOT hand-build RO URLs from the RO number); Request ID → the
Approval Workspace (/core/approval-workspace— there is NO per-request deep link,
only a?r=<ts>cache-bust param). --from-json <payload.json>renders from a cached arcapproval payload
(d['data']['hits']) — full dev/verification while the browser session is expired.--no-namesskips OpenAPI uid resolution.- Column widths for Request/RO widened so anchors never wrap — a wrapped hyperlink
splits into TWO tiny link annotation rects (bad click targets). - Approver-note attribution fixed:
notes.noteis the LATEST note; on a DRAFT/never-
approved row it's the SUBMITTER's text — only credit an approver whenapprovalTaskResponses[0].approvers[]shows an actual approval; otherwise tag
(submitter note — not yet approved).
Prereq: :9223 logged in on the target dealer (the script opens Approval Workspace itself
and steals the axios headers). Change store by passing --dealer-key (key intotekion_client config dealers).
Verify links structurally, not visually — pymupdf IS installed (pdftoppm/gs/
convert/mutool are NOT): d=pymupdf.open(path); per page pg.get_links() (each hasuri + rect from), hit-test text via pg.get_textbox(l['from']) to catch wrapped
anchors; render preview with pg.get_pixmap(dpi=140).save(...).
Audit finding this report surfaces: SELF-APPROVAL
The TL rule "Warranty Recommendation" has submitterApproval: true, so the submitter
can clear their own request even though 4 eligible approvers exist. APPR0826-000012 was
submitted AND approved by Sean Preston 1 minute apart. Always diffcreatedBy vs approvalTaskResponses[].approvers[].approverId and flag matches — that is
the single most useful control finding in this dataset. Toggle lives in Approval Setup on
the rule; do NOT change it without Joe's go-ahead.
Verifying the output PDF: ReportLab writes ASCII85 + Flate streams, so the usualzlib.decompress(stream) finds 0 streams. Usezlib.decompress(base64.a85decode(raw, adobe=True)) then regex \((.*?)\)\s*Tj.
Reading the actual generated PDF (the only real proof)
Do NOT judge by the on-screen preview. Pull the real file:
- On the RO, install a
window.open+ XHR hook, then RO kebab →
Invoice Pdf Preview (accounting copy) or View RO PDF → click the
specific doc row (e.g.Invoice - Warranty Pay - 1355955 - …). The signed S3
URL lands in your hook (/api/exports/pdf-v3then/api/media-v3/u/v2/presignedurls). - curl of that S3 URL from the host FAILS — Tekion masks the AWS key as
AKIART...Z5S6. Have the BROWSER fetch it and base64 it into a global:const r=await fetch(url); const by=new Uint8Array(await r.arrayBuffer()); let s=''; for(let i=0;i<by.length;i++) s+=String.fromCharCode(by[i]); window.__wb=btoa(s); - Pull it out in ≤16000-char slices with
substr(NOTslice— slice drops a
char at some offsets and corrupts the base64 length):for off in $(seq 0 16000 $LEN); do curl -s -X POST localhost:9223/eval \ -H 'Content-Type: application/json' -d "{\"js\":\"window.__wb.substr($off,16000)\"}" \ | python3 -c "import sys,json;sys.stdout.write(json.load(sys.stdin)['result'])" >> wb.txt; done - Tekion PDFs use a subset font with a −27 char offset, so normal text
extraction returns garbage (pdftotextis not installed;pymupdfIS installed
but text extraction from these subset fonts is still garbled — pymupdf is fine
for ReportLab-generated PDFs and link/render introspection).
Decompress the content streams, pull(...)string literals, then add 27 to
every char code. Working extractor:/tmp/pdftxt2.py+ the +27 shift, or:
Then strip''.join(chr(ord(c)+27) if 1<=ord(c)<200 else ' ' for c in raw);and spaces to get readable (if unspaced) text.
Triage order when someone says "approvals aren't on my RO PDF"
- Read
Enable RO Approval flowin Service Settings → General Setup. OFF = stop here,
nothing is being recorded so the section prints empty no matter what. - Read
Approval Liston the PDF the store actually prints. Warranty jobs → Invoice -
Warranty Pay. Confirm the RO's job pay types first viaGET /repair-orders/{rid}/jobs→payType(free, no browser) — if the jobs are
CUSTOMER_PAY there is no Approval List row on that PDF at all. - Only then check that an approval was actually requested/approved on the RO
(RO kebab → RO Bulk Action → Approval) — no approval record = empty section.
KB vs live — the discrepancy (don't chase it)
KB0010837 says: "PDF Settings → click 'Configure Section' within the Jobs section of the
body and toggle 'Show Approval Notes' to on."
That toggle does not exist on the current build. The live Jobs → Configure Section
modal contains only: Show Job Author · Show Job External Notes · Show Job Cause · Show Fee ·
Show Return RO Details · Show Tax Code · Show OEM opcode on Job Line · Display
Paytype/Payer Information (Pay Type / Payer) · Job Line/Operation (Apply To All, Show
JobLine(s), Show ServiceMenu JobLine(s), Show Other Payer Contribution) · Job Line
Configuration (Job Total Price / Total Labor / Total Parts / Total Bill Hrs) · Operation
Configuration (Show Operations, Tech Employee Cert #, Total Labor, Total Parts, Bill Hrs,
Parts Details, Parts Prices, Part Note, SOR Parts Table).
The functionality moved up to the Body-level Approval List row. Use that.
Permissions
Roles → Permissions → Service → PDF Settings: PDF Configurator View access +
PDF Configurator Edit access.
How approvals get created (so the notes have content)
RO → RO-level 3-dot kebab → RO Bulk Action → Approval → dropdown Additional Hours
→ expand job → enter note/comment, clock tag, hours requested → Save → Save the job.
The job auto-holds until approved/rejected (tech is NOT clocked out). The approver repeats
the same path and the status flips Requested → Approved/Rejected. The note/comment entered
here is what prints as the approval note.
Recommendation approvals: tech adds warranty recommendation → Submit → advisor/tech
"Request for Approval" → approver goes RO kebab → RO Bulk Action → expand → note → Approve/Reject.
Making the changes (hard-won 2026-08-25, TL)
Service Settings toggle — it SILENTLY fails the first time
Flipping Enable RO Approval flow + Submit returned no error, no toast, and a
true remount showed it back OFF. A second identical attempt saved fine
(POST /api/service-module/u/settings/service-settings → 200). Always verify with a
nav-away-and-back remount, and be prepared to repeat the flip. Do not trust the
absence of an error. Also: polling for a "toast" catches the Tekion notification
bell feed (recommendation P&A alerts) — that is NOT a save confirmation. Confirm by
re-reading the switch after remount, not by toast text.
PDF Configurator — use native .click(), not /mouse
/mouse at the switch's own getBoundingClientRect() center silently no-ops:document.elementFromPoint(x,y) resolves toDIV.root_helperText_exportText… — a transparent header overlay sits above the row.
Use row.querySelector('.ant-switch').click() instead. Same for Configure Section
(row.querySelector('button').click()).
Publishing
Save And Publish opens a confirm dialog ("Publish / Do you want to publish the
PDF? / No Yes") — click the LAST visible Yes button. Success =POST /api/servicesettings/u/pdfconfigurator → 200 with the dealerId echoed.
Verify in the PDF list: the row's Updated By / Last Updated changes to you + now,
Status stays Published.
:9223 automation notes (hard-won)
/evalpayload key isjs, notexpression./screenshotreturns JSON{"screenshot": "<base64>"}— decode it; a rawcurl -o file.png
is a JSON file andvision_analyzerejects it ("Only real image files are supported").- The
browser_*tools open a SEPARATE unauthenticated context — never mix them with :9223. - PDF rows:
[class*=inputTableRow_row]; state =.ant-switchwith classant-switch-checked. Read all rows at once:[...document.querySelectorAll('[class*=inputTableRow_row]')].filter(e=>e.offsetParent) .map(r=>{const sw=r.querySelector('.ant-switch'); return r.innerText.replace(/\n/g,'/')+' :: '+(sw?sw.className.includes('ant-switch-checked'):null);}) - The
Configure Sectionbutton will NOT respond to/mouseat its reported coords (the
page is a long virtualized scroller and coords go stale). Userow.querySelector('button').click()instead — that works reliably. - Opening a PDF: don't hand-build the configurator URL — click the row in the list
(.rt-tbody .rt-trwhose innerText starts with the PDF name). The URL carries anauditEntityIdthat is per-dealer; a copied URL from another store loads that
store's config. - Dealer switch: pill at ~(1130,32), then
/mousethe target[class*="root_dealerInfoItem_container"]row (AR 178 / AM 220 / BC 262 / BT 304 /
ST 346 / SV 388 / TL 430 / VC 472 at 1095 x). VerifylocalStorage.currentActiveDealerId. - KB SSO bootstrap:
/navigatetohttps://app.tekioncloud.com/core/knowledge-base/search.
Approval Workspace (arcapproval) — finding an approver's comment
READ THE KB FIRST (lesson learned the hard way 2026-08-25)
Joe pulled me up with "have you read all the approval workflow documents on knowledge
base?" after I'd spent many turns reverse-engineering the DOM and API. Read these
BEFORE touching the browser — they answer the architecture questions directly:
| KB | Title | Why it matters |
|---|---|---|
| KB0021275 | Core Apps - About Approval Workspace | Data model + the permission gate; only documented outputs are on-screen + Export |
| KB0021414 | FAQ on approval statuses | Pending / Completed / Declined / Withdrawn / Returned / Void / Expired |
| KB0021342 | HOW TO: Export Approvals and Requests | Export → My Approvals / My Requests / All Requests → Excel or CSV |
| KB0021217 | Core Settings - About Approval Setup | Rule setup, permissions |
| KB0021413 | Approval Management Scenarios in the Service Department | 6 service scenarios (coupon, fee, cost center, labor rate, credit limit, pay type). Note: keep "Request Expiry" OFF for Service |
| KB0021352 | Approval Management - Landing Page | Index of every related article |
Nowhere in that set is there any path from Approval Workspace to an RO/invoice PDF.
Its only documented outputs are the on-screen panel and the Excel/CSV export. Treat
"print the approver's comment on the RO PDF" as not supported until proven otherwise.
Why the approval list looks EMPTY to you but full to someone else
My Approvals (N) counts only requests awaiting your action; My Requests (N) only
ones you raised. Someone else's completed approvals are in NEITHER — so an admin can
see (0) while the submitter/approver sees (9).
The third option All Approvals only appears with the right permission. Per KB0021275
- KB0021342, at Roles → Permissions → Core → Approval Setup:
All Request View Approval WorkspaceView All Requests <Department>(e.g. Service)
Without them the dropdown shows only My Approvals | My Requests.
2026-08-25: Joe granted himself these permissions on the System Administrator role
(he did it himself rather than have Jay edit the role — consistent with the standing
"never change employee/role records without explicit approval" rule). All Approvals
now appears for jcastelino@scvolkswagen.com at TL. But the tab still read (0) until
the default Status In Pending filter was cleared — permission and filter are two
separate gates; don't stop at the first one.
Confirm WHO you are before concluding data is missing — decode the session token:
JSON.parse(atob(localStorage['t_token'].split('.')[1].replace(/-/g,'+').replace(/_/g,'/')))then resolve the uid via OpenAPI GET /users/{id} → userNameDetails.completeNames.
(2026-08-25 the :9223 session was Joe Castelino / System Administrator / CONTROLLER,
which is exactly why Sean Preston's APPR records were invisible.)
The endpoint
POST /api/arcapproval/u/approval/search?locale=en_US
{"searchText":"","filters":[],"page":{"from":0,"size":25},"tab":"ALL_APPROVALS"}tab ∈ MY_APPROVALS | MY_REQUESTS | ALL_APPROVALS.
A bare in-page fetch() gets 500 "Token doesn't exist or is invalid" — the app's
axios interceptor adds auth that can't be replicated by hand (same trap as/api/service-module/u/opcode/search). Copying t_token into authorization /Bearer / t_token headers all fail. Drive the app's own UI instead, or use the
documented Export button.
Endpoint discovery when your XHR hook returns []
If the call fired before you armed the hook, the hook stays empty and looks like nothing
happened. Use the performance resource timeline instead — it retains everything:
[...new Set(performance.getEntriesByType('resource').map(r=>r.name)
.filter(u=>/approval|workspace/i.test(u)))]That is how /api/arcapproval/u/approval/search was found. Generalise the regex to any
feature you're reverse-engineering. Do this BEFORE building elaborate hooks.
Creating a real approval request (feeds Approval List, NOT Approval Workspace)
Path: RO kebab → RO Bulk Action → Approval → Request Type ("Additional Hours"
is the ONLY option) → expand the job caret → expand the OPERATION row too → click the
operation's select cell → a row builder appears with columns:
Note/Comment | Clock Tag | Hours | Timestamp | Comment By | Status/Actions
Clock Tag options: Other Hour, Regular Hour, Diagnostic Hour, Additional Hour, Z Time.
Endpoint: POST /api/service-module/u/approval-service/request/bulk
{"addedRequests":[{"requestType":"OTHER_LABOR_HOURS","metaData":{
"operationId":"...","roId":"...","jobId":"...",
"comment":"...","timeInMillis":5400000,
"clockReason":"ADDITIONAL_TIME","instanceType":"OtherLaborHours"}}],
"voidedRequests":[],"updatedRequests":[]}Response 200 → approvalStatus: "REQUESTED", plus an approvalId.
PITFALL — 400 TAF123 err.invalid.metadata.time.in.millis: typing into the Hours
field via the /type endpoint sets the DOM value but React never commits it, sotimeInMillis is missing on submit. Force a commit before Save:
h.focus(); h.dispatchEvent(new Event('input',{bubbles:true}));
h.blur(); h.dispatchEvent(new Event('change',{bubbles:true}));Value reads back normalised (1.5 → 1.50); 1.5 hrs → timeInMillis: 5400000.
Verified result: a request at status REQUESTED still printedApproval List — Total Approval Request: 0 on a freshly generated Warranty Pay v3
PDF. So the section likely counts only completed/approved requests —
UNVERIFIED, the test request was never approved.
Process lessons (Joe corrected me twice this session)
- KB before reverse-engineering. I DOM-spelunked for many turns on something
KB0021275 states plainly. Search the KB first, then verify live. - Answer the question actually asked. Joe wanted the specific comment
"SEAN IS RAD!" located; I kept steering to my own approve-the-test-request
experiment until he said "no, I'd rather you find sean is rad". When the user
names a concrete target, chase THAT, don't substitute a tidier experiment. - Check identity before declaring data missing.
(0)vs(9)was a permissions/
persona artifact, not absent data.
Getting the comment ONTO the RO — write-path probe (2026-08-26, TL 398624)
Question: "get the approval comment onto the RO — internal note, RO note, RO media, somewhere."
Public OpenAPI cannot write ANY RO note. Verified, not assumed.
GET /repair-orders/{id}/internal-notes→ 200 (works, returns[]on this RO).POST /repair-orders/{id}/internal-notes→ 404 with{}AND with a bogus body.
404 (not 403/400) = the method does not exist. Read-only endpoint.GET /external-notes,/ro-external-notes,/notes→ all 404.- No
*extern*spec file exists in the 270-endpoint spec dump. There is no public
external-notes endpoint at all, read or write. - Grepping all specs for POST/PUT/PATCH + note/comment returns only crm-leads
(create-lead-notes,update-lead-note) and deals/customer — nothing on repair-orders.
So any note-writing automation MUST use an internal /api/service-module/... endpoint
driven from an authenticated browser, exactly like arcapproval/u/approval/search.
App-version bump DID land (2026-08-26)
Endpoints that were 403 "app version 0.0.0-pilot-2.0.0 does not support this API"
now return 200 at TL: /recommendations, /ro-warranty-claims,/internal-notes (GET). Re-probe others before assuming a block.
The comment is NOT anywhere in the public RO tree
Brute-forced 49 endpoints on RO 398624 — root, /internal-notes, /recommendations,/pricing, /ro-invoices, /ro-fees, /ro-coupons, /ro-vehicle, all 7 jobs +/job-fees + /job-sublets, every operation + /parts, and every recommendation's/operations. "SEAN IS RAD" appears in none. Only arcapproval has it.
Ranked target surfaces
| Surface | Prints on PDF? | Write path | Status |
|---|---|---|---|
Recommendation approval Note |
✅ YES — R&I Warranty prints the FULL chronological trail w/ $ deltas | advisor types it at approval time | works today, zero build — process fix |
| RO External Notes | ✅ row exists on R&I PDFs (RO External Notes, OFF at TL) |
internal endpoint, undiscovered | needs browser recon |
| RO Internal Note | ❌ no Body row found on any PDF | internal endpoint, undiscovered | UI-visible only |
| RO media | Inspection Media row exists |
unknown | unverified |
| VI media API | ❌ wrong object | POST /vehicle-inventory/{id}/media |
NOT the RO — vehicle-inventory photos, URL-reference model |
notes.mediaIds on the arcapproval note object implies approval attachments live in
arcapproval, not on the RO.
Recon command when the browser frees up
[...new Set(performance.getEntriesByType('resource').map(r=>r.name)
.filter(u=>/note/i.test(u)))]Type an RO note in the UI first, then read the timeline — same technique that found/api/arcapproval/u/approval/search.
Cross-references
tekion-service-settings, tekion-kb-search-scrape, tekion-sitemap.