Skip to content
kshivam4781Public

About

Playwright automation that fills and submits FMCSA support tickets automatically.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Ask FMCSA Ticket Submitter

Standalone Playwright script that opens Ask FMCSA in Chrome on your computer, fills a support ticket, and submits it.

Clients can be looked up in the stsdata database and their documents pulled straight off the network share — see Client search UI.

Requirements

  • Node.js 18+
  • Windows, macOS, or Linux
  • Chrome installed (falls back to Playwright Chromium if Chrome is missing)
  • For client lookup: access to the STS MySQL database and the sts-2025 share

Setup

git clone https://github.com/kshivam4781/AskFMCSA.git
cd AskFMCSA
npm install
npm run install-browsers

Copy the example env file and fill in your details:

copy config.example.env .env

On macOS/Linux:

cp config.example.env .env

Required values in .env (only when submitting without --client):

Variable Description
ASK_FMCSA_EMAIL Contact email on the ticket
ASK_FMCSA_FIRST_NAME First name
ASK_FMCSA_LAST_NAME Last name
ASK_FMCSA_DOT_NUMBER USDOT number

Optional attachments (file paths on disk):

Variable Description
ASK_FMCSA_DRIVING_LICENSE Driving license image/PDF
ASK_FMCSA_AOI Articles of incorporation

If an attachment path is omitted, the script still fills and submits the form without that file.

Client search UI

Instead of typing details by hand, look the client up in the stsdata database and let the script pull the attachments off the network share.

npm run index
npm run ui

Then open http://127.0.0.1:5178. npm run index is a one-time cache of the share folder listing and takes about three minutes over SMB; re-run it when client folders are added or renamed.

Search by company name or USDOT, click a result, review what resolved, then run a dry run or submit. The submit button is red and asks for confirmation, because it files a real ticket with FMCSA. One client at a time — there is no bulk mode.

How a client is resolved

Field Source
Company, USDOT, email stsdata.company (the database wins on conflict)
First / last name the person half of the folder, e.g. ACME INC - JOHN DOE
Driving license DL*, ACTIVE_DL*, newDL*, TEMP DL*, driver's license*
Articles of incorporation AOI*, ARTICLES OF INC/CORP/LLC*, ARTICLES*
Statement of information SOI*, STATEMENT OF INFORMATION* — only if no AOI

Folders are named COMPANY - PERSON, so the company half is matched first against the database name. The USDOT then confirms the match: the folder's INFO\INFO.txt is read, and the match is verified when that USDOT appears on the sheet. The UI shows the matched folder, the USDOT found there, and whether it agrees with the database.

A sheet may legitimately list several USDOTs — an old and a new number, or a leased authority (USING: X DBA Y ( MC: … DOT: … )). A match against any of them confirms the folder; when the number is not the sheet's primary one, the UI says so. USDOT-only lookup remains as a fallback for folders named differently from the database record.

Measured over 300 random active clients: 92% matched a folder (91% of those by company name), and of the matches whose USDOT could be checked, 99% agreed with the database. The two that disagreed were genuine data discrepancies worth flagging, not matching errors.

company.FolderLocation is deliberately ignored — it is only 17% populated and every value still points at the retired 10.1.10.210\sts2017 server.

Opening the folder

Everyone gets a way into the client folder — the quickest route to anything the UI does not surface, or to a document filed under a name the patterns miss.

Where Control What happens
At the machine running the server Open folder Explorer opens the folder directly
Anywhere else Copy folder path the full UNC path goes to the clipboard

A web page cannot open Explorer on a visitor's machine — the button would launch it on the server, where nobody would see it. Remote users therefore copy the path and paste it into Explorer's address bar or Win+R, which lands in the same folder.

Clipboard note: navigator.clipboard only exists in a secure context and this is plain HTTP over the LAN, so the copy falls back to the older execCommand route. That needs a real click — if it ever fails, the control selects the path instead so it can be copied by hand.

/api/open-folder accepts only directories inside the client share, refuses non-local callers, and spawns the file manager with an argument array and no shell, so a crafted path can be neither a different directory nor a command.

Choosing the folder by hand

About 8% of clients match no folder automatically, and a few match several. The Matched folder row always carries a search link — "Search for the folder…" when nothing matched, "Not the right folder? Search…" when something did.

It opens a type-ahead over folder names across all three buckets, seeded with the company name. Every typed term must appear in the name, so singh trans finds SINGH TRANSPORT INC - GURVINDER SINGH; exact names rank first, then prefixes, then substrings. Each suggestion shows its bucket and USDOT. Arrow keys move, Enter selects, Escape closes.

Picking a folder re-resolves the client against it — name, attachments and INFO sheet — and the row is badged chosen manually. The USDOT check still runs: choosing the wrong folder is reported rather than silently accepted.

Matched folder    BAINS XPRESS INC - BALJIT SINGH   [chosen manually]
USDOT in folder   3464691                           [differs from database]

USDOT 99999991 was not found on this folder's INFO sheet
(it lists 3464691, 3358976). Confirm this is the right client before submitting.

The re-resolve uses the original database row, not the already-merged values, so a first wrong pick cannot leak its USDOT into the second attempt.

Filename variety

Naming differs by state and by entity type. Corporations file articles or a certificate of incorporation; LLCs file of organization or of formation — so "Certificate of Organization" is the same document as "Articles of Incorporation", just a different state and entity type. Separators are optional, because "ARTICLESOFORGANIZATION" occurs too.

Sampling 700 client folders turned up 77 distinct formation-document shapes and 113 licence shapes, so the patterns are deliberately broad. The exclusions are what keep that safe:

  • DLIC is the commonest licence name on the share — 115 of 700 folders — and needs its own rule, since the "DL" word-boundary test rejects it.
  • AMERICAN DRIVING RECORDS - WEBVR.pdf is an MVR, not a driving license. ACTIVE DRIVER LIST is not one either. Both excluded.
  • Matching "certificate" at all means excluding the look-alikes, or the commonest files on the share get attached instead of the real document: MC CERTIFICATE / MC AUTHORITY (FMCSA operating authority, 28 of 700), PAYMENT SUCCESSFUL _ CALIFORNIA SECRETARY OF STATE (a fee receipt, 53 of 700), BUSINESS SEARCH _ … SECRETARY OF STATE (a search printout, 52 of 700), vehicle registrations, and CORP DISSOLUTION CERTIFICATE.
  • SOI RCPT* (a filing receipt) and EIN LETTER AND SOI* are not statements of information, and rank last. Amendments and drafts are used only if nothing better exists.

Measured over 500 random folders, the broader licence rules take detection from 45% to 64%. The formation-document rules add about 1% on top of the folder recursion, but they include the cases that were failing in practice, such as 0014129338 - Certificate of Organization.pdf.

When several files match, the one with the newest year in its name wins (SOI 2026.pdf over SOI 2021.pdf).

Where the search looks

INFO\, INFO\CORP DOCS\, INFO\DOT\ and the client folder root are searched first. For the AOI and SOI only, if none of those has one, the rest of the client folder is walked breadth-first (depth 3, capped at 300 directories) — formation documents turn up under CORP\, PERMITS\, CALIFORNIA CORPORATION\, CORP FOLDER\INFO\CORP DOCS\ and similar. The walk is lazy, so the common case costs no extra SMB round trips; a hit in INFO\ always beats one buried deeper. When a document comes from an unusual folder the UI says found in CORP next to the filename.

Measured over 400 client folders: recursing finds +11 AOIs and +7 SOIs, and takes a resolve from ~36 ms to ~103 ms.

Two things are deliberately excluded from the walk:

  • Nested client folders. Some client folders contain a folder for a different client, named the same way the top-level ones are (HWY 911 LLC - MUHAMMAD MUNIR\HX TRUCKING INC - HUI HE\INFO\ARTICLES OF INC.pdf). Recursing into those attached another company's formation document. Any subfolder whose name has a dash next to whitespace, or a run of two or more dashes, is skipped — which still searches IRP 2022-2023 and E-LOGS.
  • Drug-testing and driver paperwork (DRUG TEST\, DONOR PASS\, TEST RESULTS\, DRIVERS\, …), which accounts for most of the depth in a client folder and cannot hold a formation document.

The driving license search is not recursive, on purpose. Client folders hold employees' licences under DRIVER FILES\, DRUG TEST\DRIVERS\ACTIVE DRIVERS\<person>\ and PERMITS\EPN\DRIVERS\. Recursing would attach an employee's licence instead of the responsible person's — the wrong document, and an employee's ID sent to a federal agency. A missing DL is flagged for upload instead.

Attachments, and uploading them

Two documents go out with a ticket: the driving license and the articles of incorporation. Neither is reliably present — about 45% of folders have no AOI and roughly half have no driving license — so hand-supplying them is the common path, not an edge case.

The UI is explicit about which file is going out:

Row State Shown
Driving license found the filename, badged DL
not found no driving license found, plus a warning
uploaded the uploaded filename, badged uploaded
Articles of inc. AOI found the filename, badged AOI
no AOI, SOI found the SOI filename, badged SOI used — AOI not found, plus a warning
neither found no AOI or SOI found, plus a warning
uploaded the uploaded filename, badged uploaded

Upload DL… and Upload AOI… are available in every one of those states, so a stand-in SOI, a wrong file, or a missing one can always be overridden. An uploaded file wins over anything found on the share, and remove reverts to whatever the share had.

There is no stand-in document for a driving license the way an SOI stands in for an AOI — a missing one can only be supplied by hand.

Viewing a document before filing

Each row has a View button that opens the file in a modal, so the document can be confirmed rather than trusted on its filename — that the license belongs to the right person and has not expired, that the AOI is really articles of incorporation. The subtitle names where the file came from:

Subtitle Meaning
from the client folder on the share found automatically
statement of information, standing in for the AOI an SOI is being sent as the AOI
uploaded by you your upload, which overrides the share

PDFs and JPG/PNG render inline. TIFF is not displayable by any mainstream browser, so the modal says so and shows the full path instead of a broken frame.

/api/file serves only from the client share and the upload staging directory, and only PDF/JPG/PNG/TIFF. The browser supplies the path, so it is untrusted: symlinks are resolved before the check, and anything outside those roots is refused with a 403. INFO.txt — which holds SSNs and portal passwords — is inside the share but not a previewable type, so it is refused too.

The confirmation dialog names both attachments before a live submit:

SIAN MOTORS INC
USDOT 3399837
MAKHAN SINGH
waheguruji181020@gmail.com

DL:  NOT FOUND — nothing will be attached
AOI: NOT FOUND — sending SOI 2026.pdf instead

That is the last point at which an SOI going out as articles of incorporation, or a ticket going out with no documents, can be caught.

Uploads are staged in a temp directory under their original filename, since that name is what FMCSA sees on the attachment; a directory component in a supplied name is stripped. Nothing is written back to the share: this tool reads client folders, it does not modify them.

Drag and drop

Drop a file anywhere on the page and it goes to the right slot. The filename is classified with the same patterns the folder scanner uses, so a file it would have picked up as a licence is routed as one:

Dropped Goes to
DL.pdf, newDL.pdf, ACTIVE_DL2025.pdf, DRIVERS LICENSE.pdf Driving license
AOI.pdf, ARTICLES OF INC.pdf, ARTICLE OF LLC.jpg, SOI 2026.pdf Articles of inc.
scan_0042.pdf, IMG_4821.jpg, AMERICAN DRIVING RECORDS - WEBVR.pdf asks you

There is no drop overlay and no confirmation message: the attachment row updating is the feedback. Dropping before a client is selected does nothing.

Every drop is confirmed. The prompt always opens, with the detected slot highlighted and focused so confirming is one keypress — but a person still says yes. The other slot is one click away, and Cancel discards the file. Putting a client's licence on a federal ticket as their articles of incorporation is worth one extra click to avoid.

Several files can be dropped at once — each is classified independently, so the licence and the AOI can go together in one drop. A failed upload is logged to the browser console rather than shown on the page.

Submission log

Every attempt is appended to .submissions.jsonl (one JSON object per line, gitignored). Selecting a client shows when a ticket was last filed for them, with the FMCSA reference number if it could be read off the confirmation page. The Submission log button in the header lists everything filed.

Dry runs are recorded but flagged, and never count as "last submitted" — a rehearsal must not read as a filed ticket. Failed attempts are kept too, so the log is an audit trail rather than a success list.

tail -f .submissions.jsonl

The log is local to this machine. If the team needs shared visibility it should move to a table in stsdata.

Missing required fields

A ticket needs an email, first name, last name and USDOT. Anything that could not be resolved is shown as an empty red field with its own prompt, the submit button stays disabled, and a banner lists what is outstanding. Fill them in and the button unlocks. The server re-validates before launching Chrome, so a malformed USDOT or email cannot reach the FMCSA form.

CLI equivalents

node clients.js --find="ALL FREIGHT"
node clients.js --dot=3902833
node submit-ticket.js --client="ALL FREIGHT INC - SAQIB KHAN" --dry-run
node submit-ticket.js --client-dot=3902833

--client refuses to guess when a name matches more than one folder; it lists the candidates so you can narrow it.

Hosting on the office LAN

By default the server listens on 127.0.0.1 and only this machine can reach it. To let colleagues use it, set one value in .env:

UI_HOST=192.168.1.123

Use the machine's specific LAN address, not 0.0.0.0 — this host also has VirtualBox and WSL interfaces that should not be listening.

Then allow the port through Windows Firewall, scoped to the office subnet:

netsh advfirewall firewall add rule name="Ask FMCSA UI" dir=in action=allow protocol=TCP localport=5178 remoteip=192.168.1.0/24

Colleagues open http://192.168.1.123:5178. There is no password: access is controlled by the network itself. Anyone who can reach the port can read client documents and file tickets, so keep this on a trusted LAN and never port-forward it.

What changes when it is shared

Local only Served on the LAN
Chrome for submissions visible window headless (set UI_HEADLESS=false to see it)
Submissions run immediately queued, one at a time
Open folder shown hidden — Explorer would open on the server
Submission log this machine shared by everyone, with the source address on each row

Submissions are serialised because each one starts a Chrome on the host; two people clicking at once would otherwise race each other on the FMCSA form.

Concurrent users

A submission is validated and accepted immediately, then runs when its turn comes. Everything is checked before the job is accepted — the four required fields, and that each attachment still exists — so a request that could never succeed is rejected outright instead of taking up a place in the queue.

Only one ticket is filed at a time: each starts a Chrome on the host, and two running together would race each other on the FMCSA form. While waiting, the operator sees Queued — 2 submissions ahead of this one.

The queue is shared, so everyone sees the same picture. The header shows what is running (LOTT LOGISTICS INC submitting · 2 waiting) and the submission log lists in-progress jobs above the history, with the address each came from.

How submission is detected

On this form Continue files the ticket. The same button becomes a disabled "Submitting…" and the page goes to /app/ticket_confirm/refno/<reference>/, then redirects onward to fmcsa.dot.gov within a second or two. There is no separate Submit step.

Success is therefore decided by the confirmation reference, never by a button click:

  • A navigation watcher is armed before the form is touched, so the reference cannot be missed while the confirmation page redirects away.
  • If the run errors after a reference appears, the output says the ticket WAS filed despite the error above — do not resubmit.
  • The server marks a job successful whenever a reference was captured, even on a non-zero exit. Reporting a real filing as a failure invites someone to file it a second time.

--submit=false no longer clicks Continue at all, because clicking it would file the ticket. To fill the form without filing, use --dry-run.

Filing uploads back to the share

An uploaded or dropped document is also copied into the client folder, so it is found automatically next time and nobody has to upload it twice. This is the only place the tool writes to the share.

It goes into INFO when that exists, otherwise the client folder root. No directory is created — inventing folder structure on the share is not this tool's job.

The name decides whether the scanner finds it later, so:

Uploaded as Saved as Why
Certificate of Organization 2026.pdf unchanged the name already says what it is
DLIC new.pdf unchanged recognised by the licence patterns
scan_9911.pdf DL 2026-09-01.pdf meaningless name, canonicalised so it is matched
a second scan_9912.pdf the same day DL 2026-09-01 (2).pdf never overwrites

Nothing is ever replaced or deleted. A name that already exists gets (2), (3), and so on. There is no code path that opens a file for writing or removes one: the only operation is a copy to a name shown not to exist. Losing a client's only copy of a document to a careless upload would be far worse than an extra file in the folder.

A failed write cannot block a ticket. The submission always uses the staged temp copy, so a full or read-only share still files the ticket — the attachment row just says it was not saved, and why.

Saving happens on upload, not on submit. Pressing remove clears the file from the form; it does not delete anything from the share.

Repeat submissions

Tickets are normally filed no sooner than 7 days apart for the same client. Inside that window the history banner turns amber, and the confirmation names the problem:

⚠ A ticket was already submitted for this client 3 days ago (reference 250828-000123).
  Tickets are normally filed no sooner than 7 days apart.

⚠ A submission for this client is running right now from 192.168.1.50.
  Submitting again would file a second ticket.

The second warning matters on a shared tool: a job that is queued or running is not in the log yet — the row is written when it finishes — so the log alone cannot catch two people filing for the same client at once.

Neither warning blocks. Confirming goes ahead, because a repeat is sometimes right: the first attempt failed, or FMCSA rejected it. Dry runs never trigger a warning and never raise one for anyone else.

Change the window with RESUBMIT_AFTER_DAYS in public/index.html.

Chat notifications

When a real ticket is filed, a message goes to the Google Chat space:

✅ Ask FMCSA ticket submitted — BAINS XPRESS INC
USDOT: 3464691
Contact: BALJIT SINGH
Email: singhbaljit171@yahoo.com
Reference: 250831-000123
Submitted from: 192.168.1.57

Failures are announced too, with the error instead of a reference. Dry runs are never announced — nothing was filed, so saying otherwise would be wrong.

Set CHAT_WEBHOOK_URL in .env to enable it; unset, notifications are skipped. The URL carries a key and a token, so it never belongs in committed files. A chat outage cannot fail a submission: the log row is written first, and a webhook error is reported in the run output but the ticket still counts as filed.

What the LAN gives you, and what it does not

Access is controlled by the network alone. Anyone who can reach the port — including a guest device on the same Wi-Fi — has the app in full, and inherits the host machine's share access without needing their own.

There are no user accounts, so the submission log records the address a request came from, which is the only trace of who filed a ticket.

Traffic is plain HTTP. On a trusted office LAN that is defensible; over a VPN or anything wider, it needs HTTPS.

Configuration and secrets

The share is read using your existing Windows session. Database credentials live in .env, which is gitignored — keep them out of committed files:

STS_DB_HOST=192.168.1.115
STS_DB_PORT=3306
STS_DB_USER=remote_user
STS_DB_PASSWORD=...
STS_DB_NAME=stsdata
STS_CLIENTS_ROOT=\\192.168.1.88\sts-2025\STS\CLIENTS2025\CLIENTS

Client data warning. INFO\INFO.txt on the share contains SSNs, EINs, DOT PINs and plaintext portal passwords. clients.js reads only the USDOT and the contact email and ignores everything else — keep it that way. The UI binds to 127.0.0.1 only and must not be exposed on the network. .client-index.json holds client names and USDOT numbers and is gitignored.

Run

npm start

Or pass values on the command line (overrides .env):

node submit-ticket.js --email=you@example.com --first-name=Jane --last-name=Doe --dot=3182953

The browser stays open after submit. Press Enter in the terminal to close it.

Flags

Flag Description
--client Resolve everything from a share folder name
--client-dot Resolve everything from a USDOT (needs the index)
--email Contact email
--first-name First name
--last-name Last name
--dot USDOT number
--dl Driving license file path
--aoi Articles of incorporation file path
--dry-run Fill the form but do not click Continue/Submit
--submit=false Stop on the review page after Continue
--keep-open=false Close the browser automatically when finished

After a real submit the script prints reference: … when it can read the incident number off the confirmation page; the UI stores that in the log.

Values resolve in this order: explicit flag → client folder → .env.

Examples:

node submit-ticket.js --dry-run
node submit-ticket.js --submit=false

What it fills

On https://ask.fmcsa.dot.gov/app/ticket:

  1. Email, first name, last name, USDOT
  2. Inquiry type: Login Assistance → Request assistance logging in to NCCDB, ELD, DataQs or other FMCSA System
  3. Question: DOT LINK TO MOTUS ERROR - {USDOT}
  4. Driving license and articles of incorporation, when available
  5. Continue, then Submit on the review page

The form element IDs are Oracle RightNow widget IDs (rn_TextInput_5_…). They are position-dependent: if FMCSA adds or reorders a field, every selector breaks at once and the script fails with visibility timeouts.

Other env options

See config.example.env for:

  • ASK_FMCSA_CLICK_SUBMIT
  • ASK_FMCSA_KEEP_OPEN
  • ASK_FMCSA_DRY_RUN
  • HEADLESS
  • WAIT_TIMEOUT_MS
  • BROWSER_CHANNEL (chrome or chromium)
  • BROWSER_TIMEZONE

About

Playwright automation that fills and submits FMCSA support tickets automatically.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages