Exporting Manifest Job History to Excel with Python
This article walks throughjob_history_export.py, a command-line script that pulls completed job records from the Taqtile Manifest API and writes them to a formatted Excel spreadsheet. It also explains how you can adapt the script to build other reports against the same API.
What the Script Does
At a high level, the script:- Authenticates with the Manifest REST API to get a JWT token
- Pages through all completed jobs via GraphQL
- Pages through all meter evidence records via GraphQL
- Filters both datasets in memory by optional username and/or date range
- Writes each evidence record as a row in an
.xlsxfile with bold column headers and auto-sized columns
job_history_20250325_143012.xlsx).
job_history_export.py file:
Requirements
- Python 3.7+
- requests — HTTP client for the REST auth call and GraphQL queries
- openpyxl — reads and writes
.xlsxfiles without needing Excel installed
Usage
YYYY-MM-DD format.
How It Works, Step by Step
1. Authentication
The script POSTs credentials to the Manifest REST endpoint:user.token, which is a JWT used as the Authorization header on every subsequent GraphQL request.
2. Fetching Completed Jobs (Paginated)
All completed jobs are fetched with a single GraphQL query, paginated in batches of 100:extensions.allPages field that tells the script the total number of pages. The loop continues until all pages have been fetched.
Note on dates:completionDateandstartDateare Unix timestamps in milliseconds, returned as strings. The script converts these withdatetime.fromtimestamp(int(ms) / 1000).
3. Fetching Meter Evidence (Paginated)
A second paginated query fetches all meter evidence records frommetersEvidenceReport. These are indexed into a dictionary keyed by jobStepId so they can be joined with the steps from Step 2:
4. Client-Side Filtering
Because the API doesn’t support server-side filtering by user or date range on thejobs query, filtering happens in Python after all data is fetched:
- Username filter — compares
assignedUserDetails.emailagainst--username - Date range filter — converts
completionDatemilliseconds to adatetime.dateand compares against--start-date/--end-date
5. Writing the Excel File
The script usesopenpyxl to create a workbook with a single sheet called Job History. The columns are:
| Column | Source |
|---|---|
| Job ID | job.id |
| Job Title | job.title |
| Status | job.status |
| Priority | job.priority |
| Template | job.template.title |
| Assigned User | firstName + lastName |
| User Email | assignedUserDetails.email |
| Started At | job.startDate (converted) |
| Completed At | job.completionDate (converted) |
| Step ID | step.id |
| Step Title | step.title |
| Step Completed | step.completed |
| Note Type | "meter" (hardcoded for meter evidence rows) |
| Note Title | meterName |
| Note Text | (blank for meter evidence) |
| Evidence Meter Value | meterEvidence |
| Evidence Date | timeOfRecording (converted) |
| File Name | (blank for meter evidence) |
| File URL | (blank for meter evidence) |
| File Type | (blank for meter evidence) |
Adapting the Script for Other Reports
The script is structured in clearly separated phases — auth, query, filter, write — making it straightforward to swap in different queries or output shapes.Change the GraphQL Query
The queries are plain multi-line strings assigned to variables (JOB_QUERY, METER_EVIDENCE_QUERY). To report on a different resource, replace the query string and update the data extraction path:
Add or Remove Columns
Thecolumns list defines the header row. Add, remove, or reorder entries there, then match the same changes to the ws.append(...) call that writes each data row. Keeping both in sync is the only constraint.
Add Server-Side Filters
Some Manifest queries accept filter arguments directly. If the query you’re using supports them, pass them as GraphQL variables rather than filtering in Python — this reduces the amount of data transferred:Write Multiple Sheets
openpyxl supports multiple sheets in a single workbook. Create additional sheets before saving:
Change the Output Format
Swapopenpyxl for the csv module to write a flat CSV instead:
Point at a Different Environment
TheBASE_URL constant at the top of the script controls the target environment. Change it to switch between staging and production:
Security Note
The script currently has credentials hardcoded as string literals. Before sharing or deploying the script, move them to environment variables:Summary
job_history_export.py demonstrates the typical pattern for building Manifest reports:
- Authenticate once via REST to get a token
- Page through the relevant GraphQL queries until all data is fetched
- Join or filter the data in Python
- Write the result to Excel with
openpyxl
