API Documentation

REST API for Mercedes DFE data extraction, credit monitoring, wholesale tracking, enquiry management, and analytics.

Getting Started

1

Get an API Key

Go to Settings in the web app and scroll to the API Keys section. Click "Create New Key", give it a name, and copy the key. The key is shown only once — store it securely.

Or via API: POST /api/v1/keys (requires web session auth)

2

Authenticate with DFE

For DFE data access (markets, extraction), get a session token:

curl -X POST /api/v1/auth/login \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"username": "YOUR_DFE_USERNAME", "password": "YOUR_DFE_PASSWORD"}'

Response: {"session_token": "abc123...", "expires_at": "..."} (valid for 8 hours)

3

Make API Calls

Include the API key (and session token for DFE endpoints) in every request:

# Endpoints requiring only API key (files, history) curl /api/v1/files -H "X-API-Key: YOUR_API_KEY" # Endpoints requiring API key + session token (markets, extraction) curl /api/v1/markets \ -H "X-API-Key: YOUR_API_KEY" \ -H "X-Session-Token: YOUR_SESSION_TOKEN" # Internal endpoints (no auth needed, same-origin) curl /api/credit/current curl "/api/enquiries/vehicles?group=C-Class&page=1"

Authentication

Three authentication methods depending on the endpoint type:

API Key X-API-Key Header

Required for all /api/v1/* external endpoints. Pass via X-API-Key header or ?api_key= query param. Create keys in Settings.

Session X-Session-Token Header

Required for DFE operations (markets, dealers, extraction). Get a token via POST /api/v1/auth/login. Expires in 8 hours. Always requires API key too.

POST /api/v1/auth/login API Key Get session token for DFE access

Headers

X-API-Key: your_api_key Content-Type: application/json

Request Body

{ "username": "your_dfe_username", "password": "your_dfe_password" }

Response (200)

{ "success": true, "session_token": "abc123...", "expires_at": "2026-02-24T20:00:00", "user_id": "D6SAFAKH" }
POST /api/v1/auth/logout Session Invalidate session token

Headers

X-API-Key: your_api_key X-Session-Token: your_session_token

Response

{"success": true, "message": "Logged out successfully"}
GET /api/v1 API info and version
{ "name": "Mercedes DFE Automation API", "version": "1.0.0", "description": "Standalone REST API for Mercedes DFE data extraction", "documentation": "https://host/api/docs", "endpoints": {"auth": "/api/v1/auth", "markets": "/api/v1/markets", "files": "/api/v1/files", "history": "/api/v1/history"} }
POST /api/auth/login Web GUI login (cookie session)

Request Body

{"username": "your_username", "password": "your_password", "remember": true}

Response

{"success": true, "user_id": "D6SAFAKH"}
GET /api/auth/status Check auth status
{"authenticated": true, "user_id": "D6SAFAKH", "has_saved_credentials": true}

Sections

Markets & Dealers

GET /api/v1/markets Session Get available markets
{"markets": [{"mpc": "EG02", "displayURL": "Egypt"}]}
GET /api/v1/markets/{mpc}/dealers Session Get dealers for a market
ParamTypeDescription
mpcstringMarket code (e.g. EG02)
{"dealers": [{"dealerId": "EGC40000", "dealerName": "Abou Ghaly Motors"}], "market": "EG02"}
GET /api/v1/dealers/{mpc}/{dealer_id}/groups Session Get vehicle groups for a dealer
{"groups": [{"label": "C-Class", "vehicle_type": "MB CCLASS"}], "market": "EG02", "dealer_id": "EGC40000"}

Extraction

POST /api/v1/extraction/start Session Start extraction job

Request Body

{ "markets": ["EG02"], "all_dealers": true, "all_groups": true, "lob_codes": ["01", "03", "07", "11", "EQ"], "download_mode": "collective" }

Response

{"success": true, "job_id": "20260224_120000", "message": "Extraction started."}
GET /api/v1/extraction/status Session Get extraction progress
{"status": "running", "job_id": "20260224_120000", "overall_progress": 45.5, "markets_done": 1, "markets_total": 2, "vehicles_downloaded": 150, "elapsed_time": "00:05:30"}
POST /api/v1/extraction/stop Session Stop extraction
{"success": true, "message": "Stop signal sent"}

Files

GET /api/v1/files API Key List downloaded files
{"files": [{"name": "enquiries_20260224.json", "size": 1048576, "size_formatted": "1.0 MB", "modified": "2026-02-24T12:30:00"}], "count": 1}
GET /api/v1/files/{filename} API Key Download file as JSON (or ?raw=true)

Returns parsed JSON by default. Add ?raw=true for raw file download.

GET /api/v1/files/{filename}/excel API Key Convert JSON file to Excel (.xlsx)

Returns an Excel binary with styled headers and auto-adjusted columns.

DELETE /api/v1/files/{filename} API Key Delete file
{"success": true, "message": "File enquiries.json deleted"}

Credit Balance

GET /api/credit/current Latest balance with change info
{"has_data": true, "balance": 1250000.00, "credit_limit": 5000000.00, "total_receivables": 3200000.00, "special_liabilities": 0.00, "credit_exposure": 3750000.00, "balance_change": -150000.00, "last_updated": "2026-02-24T14:30:00", "source": "scheduled"}
GET /api/credit/history Balance snapshots with date filtering
ParamTypeDefaultDescription
date_fromYYYY-MM-DDtodayStart date
date_toYYYY-MM-DDtodayEnd date
changes_onlybooltrueOnly snapshots where balance changed
pageint1Page number
per_pageint20Items per page
GET /api/credit/chart-data Time-series data for charts
ParamTypeDescription
dateYYYY-MM-DDSingle date (or use date_from/date_to)
date_fromYYYY-MM-DDRange start
date_toYYYY-MM-DDRange end
{"labels": ["14:00", "14:30"], "datasets": {"balance": [1400000, 1250000], "credit_limit": [5000000, 5000000]}, "multi_day": false, "count": 2}
POST /api/credit/refresh Cooldown Trigger balance check

Subject to 1-minute cooldown. Returns 429 if in cooldown.

Wholesale Tracking

GET /api/wholesale/today Today's batch with vehicles
{"has_data": true, "batch": {"id": 42, "timestamp": "...", "total_vehicles": 15, "new_vehicles": 3, "balance_before": 1400000, "balance_after": 1250000, "estimated_batch_cost": 150000, "source": "scheduled"}, "vehicles": [{"commission_number": "EG123456", "vin": "WDD2130...", "model_description": "C 200", "wholesale_date": "2026-02-24", "is_sale": true, "resolved_price": 850000, "price_source": "actual", "price_badge": "Actual"}]}
GET /api/wholesale/summary Lightweight summary
{"has_data": true, "total_vehicles": 15, "new_vehicles": 3, "last_updated": "2026-02-24T10:00:00"}
GET /api/wholesale/history Paginated batch history
ParamTypeDescription
date_from / date_toYYYY-MM-DDFilter by vehicle wholesale date
page / per_pageintPagination
GET /api/wholesale/batch/{batch_id}/vehicles Vehicles for a specific batch
ParamTypeDescription
batch_idintBatch ID (path)
date_from / date_toYYYY-MM-DDOptional wholesale date filter
GET /api/wholesale/vehicles Search/filter across all batches
ParamTypeDescription
searchstringSearch commission, VIN, model
date_from / date_toYYYY-MM-DDWholesale date range
page / per_pageintPagination
POST /api/wholesale/refresh Cooldown Trigger wholesale scrape

Subject to 1-minute cooldown.

POST /api/wholesale/explore Live wholesale explorer (no cooldown)

Live-query the DFE wholesale report. Requires active DFE login. No cooldown.

Request Body

{"date_from": "20260201", "date_to": "20260224", "division": "01", "material_type": "MBPC", "grouping": "model_class", "report_type": "period", "dealer_summary": true, "class_summary": true}

Response

{"success": true, "vehicles": [...], "count": 15, "new_count": 3, "batch_id": 42, "filters": {...}}

Enquiries

GET /api/enquiries/summary Lightweight summary
{"has_data": true, "total_vehicles": 245, "group_count": 12, "last_updated": "...", "total_value": 185000000}
GET /api/enquiries/latest Latest snapshot per group
{"snapshots": [{"id": 1, "snapshot_date": "2026-02-24", "vehicle_group": "C-Class", "vehicle_count": 35}], "total_vehicles": 245, "group_count": 12}
GET /api/enquiries/vehicles Paginated vehicles with filtering & sorting

Query Parameters

ParamTypeDefaultDescription
groupcsv-Vehicle group(s)
nstcsv-Model number(s) / NST
searchstring-Search commission, VIN, model, location, salesperson
colourcsv-Colour filter
locationcsv-Location filter
sales_personcsv-Sales person (use "(No Sales Person)" for null)
sales_typecsv-Sales type (use "(No Sales Type)" for null)
price_min / price_maxnumber-Wholesale price range
stock_date_from / stock_date_toYYYY-MM-DD-MBEg stock date range
days_min / days_maxint-Days in stock range
model_yearcsv-Model year filter (e.g. MY26,MY27)
sortstringcommission_numberSort column
orderasc/descascSort order
page / per_pageint1 / 20Pagination

Sort columns: commission_number, vin, model_description, model_year, colour, wholesale_price, mbeg_stock_date, location, sales_person_name

Response includes: vehicles, pagination, and metrics (total_count, total_value, group_count, group_stats per-group breakdown).

GET /api/enquiries/filter-options Distinct values for column filters
{"colours": ["040", "149"], "colour_map": {"040": "Black", "149": "Polar White"}, "locations": ["Cairo", "Alexandria"], "sales_persons": ["(No Sales Person)", "Ahmed"], "sales_types": ["(No Sales Type)", "Fleet"], "model_years": ["MY26", "MY27"], "price_min": 500000, "price_max": 8000000, "stock_date_min": "2025-06-01", "stock_date_max": "2026-02-20"}
GET /api/enquiries/groups Vehicle groups with NST options
{"groups": [{"label": "C-Class", "vehicle_type": "MB CCLASS", "vehicle_count": 35, "national_types": [{"label": "C 200", "value": "20543"}]}], "source": "dfe"}

source: "dfe" (live), "cache", or "snapshots" (fallback)

POST /api/enquiries/refresh Cooldown Trigger enquiry scrape

Subject to 1-minute cooldown.

GET /api/enquiries/new-allocations New allocations for a date
ParamTypeDefault
dateYYYY-MM-DDtoday
{"date": "2026-02-24", "total_new": 5, "groups": {"C-Class": {"vehicles": [...], "count": 3, "total_value": 2550000}}}
GET /api/enquiries/stats Per-group statistics
ParamTypeDefault
dateYYYY-MM-DDtoday
{"date": "2026-02-24", "stats": [{"vehicle_group": "C-Class", "current_count": 35, "previous_count": 32, "new_today": 3}], "summary": {"total_current": 245, "total_previous": 240, "total_new": 5}}
GET /api/enquiries/diff/{date_str} Diff view for a date
{"date": "2026-02-24", "total_new": 5, "groups": {"C-Class": {"new_vehicles": [...], "removed_commissions": [], "new_count": 3, "total_value": 2550000}}}
GET /api/enquiries/export Download filtered vehicles as Excel

Same filter params as /api/enquiries/vehicles, plus preset name. Returns .xlsx with one sheet per vehicle group, plus additional tabs grouped by Model Year.

Enquiry Analytics

All analytics endpoints accept the same vehicle filter params as /api/enquiries/vehicles.

GET /api/enquiries/analytics/kpis KPI dashboard metrics
{"total_vehicles": 245, "total_value": 185000000, "avg_price": 755102, "vehicle_groups": 12, "new_today": 5, "avg_days_in_stock": 42.3}
GET /api/enquiries/analytics/distribution Distribution by dimension
ParamTypeDefaultOptions
dimensionstringvehicle_groupvehicle_group, colour, location, sales_person_name, vehicle_usage_desc, sales_type_desc, damage_level, model_description, trim
{"dimension": "colour", "labels": ["Black", "White"], "counts": [45, 38], "values": [34000000, 28500000]}
GET /api/enquiries/analytics/trend Count + value over time
ParamTypeDefault
date_fromYYYY-MM-DD30 days ago
date_toYYYY-MM-DDtoday
groupcsvall
{"dates": ["2026-01-25", ...], "counts": [230, ...], "values": [173000000, ...]}
GET /api/enquiries/analytics/allocations-trend New allocations per day
ParamType
date_from / date_toYYYY-MM-DD
group, colour, locationcsv
GET /api/enquiries/analytics/price-distribution Price histogram
ParamTypeDefault
binsint10 (3-30)
{"bin_labels": ["500k - 700k", ...], "bin_ranges": [[500000, 700000], ...], "counts": [45, ...]}
GET /api/enquiries/analytics/aging Inventory aging analysis
{"buckets": ["0-7 days", "8-14 days", "15-30 days", "31-60 days", "61-90 days", "90+ days"], "counts": [12, 8, 25, 45, 30, 125], "values": [...]}
GET /api/enquiries/analytics/top-groups Top N vehicle groups
ParamTypeDefault
metricstringcount (or value)
limitint10 (1-50)
GET /api/enquiries/analytics/filter-options Filter options for analytics
{"vehicle_groups": [...], "colours": [...], "locations": [...], "sales_persons": [...], "sales_types": [...], "vehicle_usages": [...], "damage_levels": [...]}

Filter Presets

GET /api/presets?type={type} List presets by type
ParamTypeDescription
type requiredstringe.g. enquiry_filter, wholesale_filter
{"presets": [{"id": 1, "name": "My Filter", "preset_type": "enquiry_filter", "filters": {...}, "use_count": 5, "created_at": "...", "last_used_at": "..."}]}
POST /api/presets Create preset
// Request: {"name": "My Filter", "preset_type": "enquiry_filter", "filters": {"group": "C-Class"}} // Response (201): {"id": 1, "name": "My Filter", ...}
PUT /api/presets/{preset_id} Update preset
{"name": "Updated Name", "filters": {"group": "E-Class"}}
DELETE /api/presets/{preset_id} Delete preset
{"success": true}
POST /api/presets/{preset_id}/apply Apply preset (increment use count)

Increments use_count and returns preset with filters.

Dashboard & Scheduler

GET /api/dashboard/credit-balance Credit balance widget
{"has_data": true, "balance": 1250000, "credit_limit": 5000000, "balance_change": -150000, "change_direction": "down", "last_updated": "..."}
GET /api/dashboard/today-summary Today's operations summary
ParamTypeDefault
dateYYYY-MM-DDtoday
{"has_data": true, "is_live": true, "balance_trend": "down", "cars_bought": 3, "cars_sold": 1, "new_allocations": 5, "opening_balance": 1400000, "closing_balance": 1250000, "total_spent": 150000}
GET /api/dashboard/scheduler-status Scheduler status
{"scheduler_enabled": true, "scheduler_mode": "standalone", "jobs": [{"type": "check_balance", "name": "Check Balance", "last_run": "...", "last_status": "completed"}]}
POST /api/scheduler/trigger/{job_type} Cooldown Trigger scheduler job

Valid types: check_balance, scrape_wholesale

// 200: {"success": true, "job_id": 123, "message": "check_balance triggered"} // 429: {"success": false, "error": "Job is in cooldown period", "remaining_seconds": 845}
GET /api/scheduler/cooldowns Cooldown status for all jobs
{"cooldowns": {"check_balance": {"allowed": true, "remaining_seconds": 0}, "scrape_wholesale": {"allowed": false, "remaining_seconds": 845}}}

Notifications

GET /api/notifications List notifications
ParamTypeDefault
limitint20
{"notifications": [{"id": 1, "timestamp": "...", "title": "Balance Changed", "message": "...", "level": "warning", "read": false, "dismissed": false}]}
GET /api/notifications/count Unread count
{"unread_count": 3}
POST /api/notifications/{notif_id}/read Mark as read
{"success": true}
POST /api/notifications/{notif_id}/dismiss Dismiss
{"success": true}
POST /api/notifications/mark-all-read Mark all as read
{"success": true}

Insights & Pricing

GET /api/insights/today Live today's summary
{"date": "2026-02-24", "is_live": true, "balance_trend": "down", "opening_balance": 1400000, "closing_balance": 1250000, "total_spent": 150000, "cars_bought": 3, "cars_sold": 1, "new_allocations": 5, "bought_details": [...], "sold_details": [...]}
GET /api/insights/{date_str} Summary for a date

Path param date_str in YYYY-MM-DD. Returns persisted or live summary.

GET /api/insights/{date_str}/events Day events
{"date": "2026-02-24", "events": [{"timestamp": "...", "type": "wholesale_purchase", "description": "3 vehicles purchased"}]}
GET /api/insights/range Date range summaries (max 90 days)
ParamTypeDescription
from requiredYYYY-MM-DDStart
to requiredYYYY-MM-DDEnd
GET /api/insights/month/current Live current month summary

Aggregates daily summaries for the current month. Returns month-level totals plus a daily_breakdown array for charting.

{"year": 2026, "month": 3, "month_label": "March 2026", "is_current": true, "opening_balance": 14625000, "closing_balance": 1567540, "total_spent": 148778687, "cars_bought": 38, "cars_sold": 0, "new_allocations": 905, "days_with_activity": 14, "daily_breakdown": [...]}
GET /api/insights/month/{year_month} Summary for a specific month
ParamTypeDescription
year_monthYYYY-MMTarget month (path param)

Same response shape as /api/insights/month/current.

GET /api/insights/months Multi-month lightweight summaries (max 12)
ParamTypeDescription
from requiredYYYY-MMStart month
to requiredYYYY-MMEnd month

Returns totals only (no daily_breakdown) for overview grids.

GET /api/vehicles/{commission_number}/price Vehicle price breakdown
{"commission_number": "EG123456", "active_price": 850000, "active_source": "actual", "sources": [{"source": "actual", "badge": "Actual (Billed)", "amount": 850000, "date": "2026-02-24", "is_active": true}], "discrepancy": null}

Job History

GET /api/history/jobs Paginated job history
ParamTypeDefault
typestringall
page / per_pageint1 / 20
{"jobs": [{"id": 1, "job_type": "check_balance", "status": "completed", "started_at": "...", "completed_at": "...", "duration_seconds": 45}], "total": 150, "page": 1, "per_page": 20, "pages": 8}
GET /api/history/jobs/{job_id} Job details

Full details for a single scheduler job.

POST /api/history/jobs/{job_id}/stop Cancel running job
{"success": true, "message": "Stop requested"}
GET /api/history/daily-summaries Daily summaries
ParamTypeDefault
limitint30

Customs Documents

Upload, parse, and search Egyptian customs/traffic publication PDFs. Extracted vehicle data (VIN, declaration, outgoing numbers) is cross-linked with enquiries and wholesale records.

POST /api/v1/customs/upload API Key Upload customs PDFs (external API)

Upload one or more customs PDF files for parsing. Vehicles (VINs starting with 'W') are extracted and stored. Invoice and shipment numbers are parsed from the Arabic filename.

Request

curl -X POST /api/v1/customs/upload \ -H "X-API-Key: YOUR_API_KEY" \ -F "files=@customs_document.pdf" \ -F "files=@another_document.pdf"

Response

{"results": [{"filename": "ف 123 ش 21.pdf", "document": {"id": 1, "filename": "...", "invoice_number": "123", "shipment_number": "21", "vehicle_count": 15, "uploaded_at": "...", "uploaded_by": "api:MyKey"}, "vehicles_extracted": 15}]}
POST /api/customs/upload Upload customs PDFs (web UI)

Same as above but uses web session auth (for the drag-and-drop UI upload). Accepts multipart/form-data with files field.

Response

{"results": [{"filename": "...", "document": {...}, "vehicles_extracted": 15}]}
GET /api/customs/documents List all customs documents
ParamTypeDefault
pageint1
per_pageint15

Response

{"documents": [...], "total": 25, "page": 1, "per_page": 15, "pages": 2}
GET /api/customs/documents/{id} Document detail with vehicles
{"document": {"id": 1, "filename": "...", "invoice_number": "123", "shipment_number": "21", "vehicle_count": 15, "uploaded_at": "...", "uploaded_by": "admin"}, "vehicles": [{"id": 1, "vin": "WDD2130...", "serial_number": "5", "declaration_number": "1234", "outgoing_number": "567", "page_number": 0, "row_bbox": [50.5, 200.3, 550.2, 220.1]}]}
GET /api/customs/documents/{id}/pdf Serve or download PDF
ParamTypeDesc
downloadflagAdd ?download to force download

Returns the PDF file with Content-Type: application/pdf. Used by the PDF.js viewer for inline display.

DELETE /api/customs/documents/{id} Delete document and PDF file
{"success": true}
GET /api/customs/vehicle/{vin} Lookup customs docs for a VIN
{"vin": "WDD2130...", "customs_records": [{"document": {...}, "vehicle": {"serial_number": "5", "declaration_number": "1234", "outgoing_number": "567", "page_number": 0, "row_bbox": [...]}}]}
POST /api/customs/search Batch VIN search

Request

{"vins": ["WDD2130...", "WDD2462...", "WDC2539..."]}

Response

{"results": [{"vin": "WDD2130...", "customs": [{"document": {...}, "vehicle": {...}}], "enquiry": {"model": "C200", "location": "Stock", "price": 1500000, ...}, "wholesale": null}]}
POST /api/customs/search/upload Batch search from Excel (column Y)

Upload an Excel file (billing sheet). VINs are extracted from column Y (index 25). Returns the same cross-linked results as /api/customs/search.

curl -X POST /api/customs/search/upload -F "file=@billing.xlsx"
POST /api/customs/reparse Re-parse all documents (maintenance)

Re-parses all uploaded customs PDFs to update row bounding boxes and vehicle data. Use after deploying parser fixes.

{"updated_documents": 3, "errors": []}

Reference & API Keys

GET /api/v1/lob-codes LOB codes
{"codes": [{"code": "01", "name": "Passenger Cars", "description": "Standard passenger vehicles"}, {"code": "03", "name": "Commercial Vehicles"}, {"code": "07", "name": "Trucks"}, {"code": "11", "name": "Buses"}, {"code": "EQ", "name": "Electric Vehicles"}]}
GET /api/v1/history API Key Extraction history
ParamType
statuscompleted, failed, stopped
limitint
GET /api/v1/keys Web Session List API keys
{"keys": [{"id": "abc", "name": "Integration Key", "created_at": "...", "expires_at": null, "revoked": false, "last_used": "..."}]}
POST /api/v1/keys Web Session Create API key

Request

{"name": "My Integration", "description": "Optional", "expires_days": 365}

Response (key shown only once!)

{"success": true, "key": "dfe_k1_abc...", "key_info": {"id": "abc", "name": "My Integration"}, "warning": "Store this API key securely. It will not be shown again."}
DELETE /api/v1/keys/{key_id} Web Session Revoke API key
{"success": true, "message": "API key revoked"}

User Management

All endpoints require admin role via web session.

GET /api/users Admin List users
{"users": [{"id": 1, "username": "admin", "display_name": "Administrator", "role": "admin", "is_active": true}]}
POST /api/users Admin Create user
// Request: {"username": "newuser", "password": "pass", "display_name": "New User", "role": "user"} // Response (201): {"user": {...}}
PUT /api/users/{user_id} Admin Update user
{"display_name": "Updated", "role": "admin", "is_active": true, "password": "new_pass"}
DELETE /api/users/{user_id} Admin Delete user
{"success": true}

OpenAPI & Swagger

Full OpenAPI 3.0 specification with all endpoints, parameters, and schemas:

Open Swagger UI Download OpenAPI JSON