A Flask-based REST API for Johnson Gage and Inspection, Inc. (JGI) that serves as an internal API for quality management operations. The API integrates with Microsoft Entra ID for authentication and the Qualer quality management system for data operations.
- Base URL:
https://jgiapi.com - Documentation:
https://jgiapi.com/docs(Swagger UI) - OpenAPI Spec:
https://jgiapi.com/openapi.json
| Endpoint | Description | Purpose |
|---|---|---|
GET /whoami |
User authentication info | Returns current user details from JWT token |
GET /work-item-details |
Work item details | Fetches work item data from Qualer system |
GET /pyro-assets |
Pyrotechnic assets | Returns assets from specific pyro asset pool |
GET /employees |
Employee listing | Gets all employees from Qualer |
GET /clients |
Client companies | Returns all client companies from Qualer |
POST /git-pull |
Deployment automation | Updates server code (uses separate token auth) |
π See https://jgiapi.com/docs for complete API documentation and interactive testing
The API includes an automated background scheduler that keeps calibration and certification data synchronized with external sources:
| Data Type | Interval | Source |
|---|---|---|
| Wire Set Certificates | Every 24 hours | Qualer API + SharePoint |
| Wire Offsets | Every 24 hours | SharePoint |
| DAQBook Offsets | Every 24 hours | SharePoint |
| Wire Roll Work Items | Every 4 hours | Qualer API |
The background scheduler is enabled by default in production and automatically:
- Checks last sync times on startup
- Runs syncs at configured intervals
- Handles failures gracefully with error logging
- Tracks sync state in the database
Environment Variables:
ENABLE_SYNC_SCHEDULER=true # Enable/disable scheduler (default: true)
IS_TEST_CONTEXT=false # Auto-disables scheduler in testsYou can also trigger syncs manually via the API:
POST /data-sync?init=false
Authorization: Bearer <access_token>For more details on sync intervals and configuration, see utils/sync/state_manager.py.
This API is protected using Microsoft Entra ID via OAuth 2.0. All routes (except / and /git-pull) require a valid access token issued by the registered Azure application.
This application is registered in Azure Active Directory at:
π Azure Portal App Registration
- Client ID:
43a01068-983b-41b9-bb61-7ed191bd0e29 - Audience:
https://jgiapi.com - Scope:
access_as_user(defined under Expose an API)
GET /whoami
Authorization: Bearer <access_token>GET /work-item-details?workItemNumber=56561-067667-01
Authorization: Bearer <access_token>- Python 3.8+
- Azure AD access for authentication testing
- Qualer API access (for full functionality)
-
Clone the repository
git clone <repository-url> cd api
-
Install dependencies
pip install -r requirements.txt -
π Environment Configuration
This project uses a two-tier environment setup for security:
config/settings.env: Safe defaults (checked into Git).env: Secrets (DO NOT COMMIT)
Create your
.envfile with secrets:# .env (secrets only - DO NOT COMMIT) # Qualer API Integration QUALER_API_KEY=your-qualer-api-key # Azure Authentication AZURE_CLIENT_SECRET=your-azure-client-secret # Database Connection DATABASE_URL=postgresql+psycopg2://user:pass@host:5432/dbname # Development Settings (optional) SKIP_AUTH=false # Set to 'true' for testing without Azure AD
Safe configuration is already in
config/settings.env:- Azure client ID, tenant ID, API audience
- SharePoint site and drive IDs
- Other non-sensitive defaults
-
Run the application
Development mode (Flask dev server):
python app.py
Production mode (Waitress WSGI server):
python wsgi_server.py
The production server uses Waitress, a production-quality pure-Python WSGI server that:
- β Supports multi-threading for concurrent requests
- β Works natively on Windows
- β Provides better stability and performance than Flask's dev server
- β Includes built-in request timeouts and connection limits
Production Mode (SKIP_AUTH=false):
- Full authentication required with real Azure AD tokens
- All business logic executes against real Qualer API
- Use for integration testing and debugging
Development/CI Mode (SKIP_AUTH=true):
- Mock authentication with fake tokens
- Fast testing without external dependencies
- Ideal for unit testing and CI/CD
Use the provided helper script to acquire valid Azure AD tokens:
python utils/get_token.pyThis will open a browser for interactive Azure AD login and return a valid access token.
This project includes a comprehensive testing framework using a mark-based architecture.
# Full test suite
python -m pytest -v
# Optional: with coverage (local only)
python -m pytest --cov=. --cov-report=term-missing
# Bypass auth locally
$env:SKIP_AUTH="true"; python -m pytestpython -m pytest -vπ See DEVELOPMENT_GUIDE.md for comprehensive development and testing documentation
βββ app.py # Main Flask application
βββ config/ # Environment configuration
β βββ settings.
βββ requirements.txt # Python dependencies
βββ .env # Environment variables (not in git)
βββ routes/ # API endpoint blueprints
β βββ whoami.py # User authentication info
β βββ work_item_details.py # Work item data from Qualer
β βββ pyro_assets.py # Pyrotechnic assets
β βββ employees.py # Employee listings
β βββ clients.py # Client companies
β βββ git_ops.py # Deployment automation
βββ utils/ # Shared utilities
β βββ auth.py # Authentication decorators
β βββ qualer_client.py # Qualer SDK configuration
β βββ get_token.py # Azure AD token helper
β βββ schemas.py # Marshmallow schemas
βββ tests/ # Test suite
βββ conftest.py # Test fixtures
βββ test_*.py # Individual test files
- Create route file in
/routes/as Flask-Smorest blueprint - Add authentication with
@require_authdecorator - Define schemas for request/response serialization
- Register blueprint in
app.py - Create corresponding test file in
/tests/ - Prefer recording HTTP interactions with pytest-vcr and committing cassettes; migrate old mock implementations when convenient
- Import Organization: Standard library, third-party, local imports
- Error Handling: Use Flask-Smorest
abort()for API errors - Documentation: Include docstrings for complex business logic
- Type Hints: Use type hints for better code clarity
- URL:
https://jgiapi.com - WSGI Server: Waitress (production-grade, Windows-compatible)
- Deployment: Automated via GitHub Actions
- Proxy: Uses ProxyFix middleware for reverse proxy support
- CORS: Enabled for cross-origin requests from Excel/Power Query
The API runs using Waitress WSGI server via wsgi_server.py:
python wsgi_server.pyWhy Waitress?
- β Pure Python - works natively on Windows
- β Multi-threaded - handles concurrent requests efficiently
- β Production-stable - no Flask dev server warnings
- β Zero configuration - works out of the box
- Code Push: Push to
mainbranch triggers deployment - GitHub Action: Calls
/git-pullendpoint on production server - Server Update: Production server pulls latest code and restarts
- Health Check: Verify endpoints respond correctly
The production server uses Vector (log ingestion agent) to forward application logs to SparkLogs for centralized monitoring. Vector runs as a Windows service (VectorLogAgent) that:
- β Starts automatically on server boot
- β Restarts on failure
- β Forwards logs to SparkLogs without manual intervention
- β Parses Python logging format with multiline support (stack traces, JSON blocks)
- β Enriches logs with metadata (file, line number, logger name, host)
Vector ingests logs from:
app.logβ Main Flask application events and errorsdeployment.logβ Deployment and sync operation logs
Vector automatically:
- Parses Python logging format:
YYYY-MM-DD HH:MM:SS,milliseconds LEVEL logger [file:line]: message - Reassembles multiline events (stack traces, formatted JSON blocks)
- Extracts metadata fields: timestamp, level, logger name, source file/line
- Forwards to SparkLogs with gzip compression
# Check service status
Get-Service VectorLogAgent
# Stop the service
Stop-Service -Name VectorLogAgent
# Start the service
Start-Service -Name VectorLogAgent
# View Vector logs (if running interactively)
C:\vector\bin\vector run -c C:\vector\vector.yamlVector configuration: C:\vector\vector.yaml
- SparkLogs endpoint:
https://ingest-us.engine.itlightning.app/ingest/v1 - Data directory:
C:\vector\data(stores checkpoints for file position tracking)
- Interactive Docs:
/docs- Swagger UI for testing endpoints - OpenAPI Spec:
/openapi.json- Machine-readable API specification
- Development Guide:
DEVELOPMENT_GUIDE.md- Complete development, testing, and architecture guide - Copilot Instructions:
.github/.copilot-instructions.md- GitHub Copilot development guidance - Domain Logic:
.github/DOMAIN_LOGIC.md- Wire calibration business logic and workflows - Qualer SDK: OpenAPI Specification
- Qualer SDK: Primary integration for quality management data
- Azure AD: Authentication and user management
- Excel/Power Query: Designed for consumption by Microsoft tools
- Secret Management: Never commit API keys or secrets to git
- Environment Variables: Use environment variables for all configuration
- Input Validation: All input parameters are validated via Marshmallow schemas
- Token Security: Proper scope checking for Azure AD tokens
- Audit Logging: Security-relevant events are logged for monitoring
- Authentication failures: Check environment variables and Azure AD configuration
- Qualer API errors: Verify
QUALER_API_KEYpermissions and format - CORS issues: Ensure requests include proper headers for cross-origin access
- Testing failures: See
DEVELOPMENT_GUIDE.mdfor debugging strategies
- VS Code: Use the Testing panel for running and debugging individual tests
- Debugging: Set
SKIP_AUTH=truefor development without Azure AD setup - Logs: Check Flask application logs for detailed error information
This API is designed for easy consumption in Microsoft Excel using Power Query:
- Data β Get Data β From Web
- URL:
https://jgiapi.com/endpoint - Authentication: Choose Organizational Account
- Sign in: Use your JGI Azure AD credentials
- Load: Data loads automatically with proper authentication headers
Example Power Query M code:
let
Source = Json.Document(
Web.Contents(
"https://jgiapi.com/employees",
[Headers=[Authorization="Bearer " & AccessToken.Token]]
)
)
in
Source