Purpose
Bulk-import a fee schedule (UCR or other type) into Open Dental for an active client, using the
create_fee_schedule.py script. This SOP ensures existing prices are not overwritten
and that errors are caught before the write.
Requirements: Client with OD API enabled · CSV or PDF with the fees · Repo access + venv activated.
Visual flow
has OD API] --> B[2. List existing
fee schedules] B --> C{Schedule
exists?} C -->|No| D[Create manually
in OD UI] C -->|Yes| E[3. Note the
FeeSchedNum] D --> E E --> F[4. Prepare CSV
from PDF or manual] F --> G[5. DRY-RUN
touches nothing] G --> H{Output
OK?} H -->|Typos /
weird prices| F H -->|Codes missing
in OD| I[Enable codes
in OD Definitions] I --> G H -->|All OK| J[6. Real import
--confirm] J --> K[7. Verify
in OD UI] K --> L[8. Optional:
Assign as UCR] style A fill:#4B6AF7,color:#fff,stroke:#415EDB style G fill:#F1CCB1,color:#1a1a2e,stroke:#c9690a style J fill:#2d8659,color:#fff,stroke:#2d8659 style L fill:#415EDB,color:#fff,stroke:#415EDB
Pre-checks
| Check | How to verify |
|---|---|
| Client exists | ls clients/<client-id>.json |
| OD API enabled | In the JSON: "has_od_api": true + customer_key present |
| venv activated | source .venv/bin/activate (prompt starts with (.venv)) |
| CSV ready | Required columns: ProcCode, Amount |
Detailed steps
1Verify client and list existing fee schedules
Confirms the client is configured properly and shows which fee schedules already exist in Open Dental.
python create_fee_schedule.py <client-id> --list
FeeSchedNum Type Hidden Description ------------- -------------- ------- ---------------------------------------- 53 Normal Office Fees 57 Normal Delta Dental PPO 66 Normal Office Fee 2026 ← the new one
2Create the fee schedule (if it doesn't exist)
BrandaCare policy: fee schedules are created manually in OD UI (not via API) to avoid accidental duplicates.
In Open Dental:
- Setup → Fee Schedules → Add
- Description: clear name (e.g. "Office Fee 2026")
- Type: Normal (UCR) or Insurance (PPO)
- Save → NOTE THE FeeSchedNum
python create_fee_schedule.py <client-id> --create "Office Fee 2026" — confirm with the client first to avoid duplicate names.
3Prepare the CSV
Option A — from PDF (most common with new clients):
python parse_pdf_fee_schedule.py "Dr Smith Fees 2026.pdf" clients/smith_fees_2026.csv
The parser detects D#### Description $Amount lines and auto-flags likely typos (prices >$5000 or <$10).
Option B — manual CSV:
ProcCode,Amount,Description D0120,150.00,Periodic oral evaluation D0150,200.00,Comprehensive oral evaluation ...
4Dry-run (CRITICAL — touches nothing)
python create_fee_schedule.py <client-id> \
--csv clients/smith_fees_2026.csv \
--to <FeeSchedNum> \
--dry-run
Review the output carefully:
- OK
[dry] D0120 = $150.00— will import - SKIP
⚠ code X not found in OD— ADA code not enabled in this client - TYPO Any price >$5000 that isn't major surgery/prosthetics
At the end you see:
✅ 516 fees simulated, 17 skipped 📄 List saved to: clients/smith_fees_2026_SKIPPED.csv
5Real import — --confirm
- The fee schedule is empty in OD (if it has fees, they duplicate).
- Parser typos have been corrected.
- The FeeSchedNum matches the one from step 1.
python create_fee_schedule.py <client-id> \
--csv clients/smith_fees_2026.csv \
--to <FeeSchedNum> \
--confirm
Progress every 50 fees. Takes ~6 min per 500 fees.
6Verify in OD
- OD UI → Setup → Fee Schedules → your schedule → should show ≈ N fees created
- Spot check: search 3-4 random codes and confirm the price
- Review
<csv>_SKIPPED.csvand forward to client to enable missing codes (optional)
7Optional — Assign as provider UCR
This makes new procedures use this fee schedule as the default price. Does not affect existing procedures.
python create_fee_schedule.py <client-id> \
--assign-ucr <FeeSchedNum> \
--to-provider <ProvNum> \
--confirm
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
client not found | Typo in client_id | ls clients/ and copy the exact name |
code X not found in OD | ADA code not enabled in client | OD → Setup → Definitions → Procedure Codes → enable |
this client has no OD API | Missing customer_key in JSON | Request from client (Google Secret Manager if internal) |
| Duplicate prices post-import | Destination FeeSched wasn't empty | Delete manually in OD and re-import |
Intermittent ReadTimeout | OD API slow | Re-run — script retries automatically |
Safety rules
- DEFAULT All commands are read-only by default.
- --dry-run Always first. No exceptions.
- --confirm Required for any write. Double-check client_id and FeeSchedNum first.
- CSV Validate parser warnings before importing.
Copy-paste commands
# 1. List python create_fee_schedule.py <client-id> --list # 2. Parse PDF (if needed) python parse_pdf_fee_schedule.py "fee_schedule.pdf" output.csv # 3. Dry-run python create_fee_schedule.py <client-id> --csv path/to.csv --to <num> --dry-run # 4. Real import python create_fee_schedule.py <client-id> --csv path/to.csv --to <num> --confirm # 5. Assign as UCR (optional) python create_fee_schedule.py <client-id> --assign-ucr <num> --to-provider <prov> --confirm
Import history
| Date | Client | FeeSched | Result |
|---|---|---|---|
| 2026-06-11 | Casas Family Dentistry | 66 — Office Fee 2026 | 516 fees created, 17 skipped |
