LCO DMA - API Reference¶
Complete reference for all REST API endpoints in the LCO Data Management Application.
Base URL¶
- Local Development:
http://localhost:8000/api/v1 - Production:
https://<app-name>.azurewebsites.net/api/v1
Authentication¶
Dual Authentication Methods¶
The API supports two authentication methods (tried in order):
-
API Key - For programmatic access
-
Azure Entra ID JWT - For interactive users
Roles¶
| Role | Access |
|---|---|
| Reader | Read-only access to all resources |
| Contributor | Full read/write access |
API Key Management¶
API keys can be created via /api/v1/api-keys (requires Contributor role).
Keys use bcrypt hashing - only the first 8 characters (prefix) are stored for lookup.
Common Patterns¶
Request Headers¶
orResponse Format¶
All successful responses return JSON with camelCase field names.
Success Response (200 OK):
Error Response (4xx/5xx):
Pagination¶
List endpoints support pagination via query parameters:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
skip |
int | 0 | - | Items to skip |
limit |
int | 20 | 100 | Items to return |
Paginated Response:
Field Naming Convention¶
- Request/Response Body: camelCase (e.g.,
projectName,clientId) - Query Parameters: camelCase (e.g.,
?clientId=CLIENT-001) - Path Parameters: Standard case (e.g.,
/projects/{projectId})
Health Check¶
GET /health¶
Verify API and database connectivity.
Response:
Projects¶
GET /projects¶
List all projects with optional filters.
Query Parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| clientId | string | Filter by client |
| status | string | planning, active, completed, on-hold, cancelled |
| projectType | string | commercial, residential, industrial, etc. |
| search | string | Search in name/description |
| skip, limit | int | Pagination |
POST /projects¶
Create a new project.
Required Fields: projectCode, clientId, projectName
GET /projects/{projectId}¶
Get project details with services.
Query Parameters: clientId (required)
PUT /projects/{projectId}¶
Update project.
Query Parameters: clientId (required)
DELETE /projects/{projectId}¶
Delete project.
Query Parameters: clientId (required)
Services¶
GET /projects/{projectId}/services¶
List services for a project.
Query Parameters: clientId (required), serviceType (optional)
POST /projects/{projectId}/services¶
Create a service.
Required Fields: serviceType, serviceName
Service Types: estimation, loan-monitoring, project-controls, claims
GET /services/{serviceId}¶
Get service details.
Query Parameters: projectId (required)
PUT /services/{serviceId}¶
Update service.
DELETE /services/{serviceId}¶
Delete service.
Material Takeoff (MTO)¶
The most comprehensive API for managing construction material takeoffs.
GET /mto¶
List MTO items with filters.
Query Parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| projectId | string | Required - Filter by project |
| serviceId | string | Optional - Filter by service |
| discipline | string | Filter by discipline type |
| skip, limit | int | Pagination |
POST /mto/search¶
Advanced search with filters and sorting.
Request Body:
{
"projectId": "PROJ-001",
"serviceId": "SVC-001",
"discipline": "piping",
"filters": {
"logicalOperator": "AND",
"conditions": [
{"field": "quantity", "operator": "greaterThan", "value": 100}
]
},
"sort": [{"field": "totalCost", "direction": "desc"}],
"skip": 0,
"limit": 50
}
POST /mto¶
Create single MTO item.
PUT /mto/{mtoId}¶
Update MTO item.
Query Parameters: projectId (required)
DELETE /mto/{mtoId}¶
Delete single MTO item.
POST /mto/bulk¶
Bulk create MTO items.
Request Body:
POST /mto/bulk-edit¶
Bulk edit MTO items.
Request Body:
{
"selection": {
"mode": "ids",
"ids": ["mto-1", "mto-2"]
},
"updates": {
"crewCode": "CREW-001"
},
"projectId": "PROJ-001"
}
Selection modes: ids (list of IDs) or filters (FilterCriteria)
POST /mto/bulk-delete¶
Bulk delete MTO items.
Request Body:
{
"selection": {
"mode": "ids",
"ids": ["mto-1", "mto-2"]
},
"projectId": "PROJ-001",
"confirm": true
}
POST /mto/import/async¶
Async file import via Service Bus.
Request: multipart/form-data with Excel file
Response:
GET /mto/import/status/{taskId}¶
Poll import status.
Response:
GET /mto/export¶
Export MTO to Excel.
Query Parameters: projectId, serviceId, discipline, format (single-sheet/multi-sheet)
GET /mto/template¶
Download import template.
Query Parameters: discipline (required)
GET /mto/project/{projectId}/summary¶
Get MTO summary by discipline.
GET /mto/project/{projectId}/disciplines¶
List disciplines used in project.
GET /mto/project/{projectId}/distinct-values/{field}¶
Get distinct values for a field (for filter dropdowns).
Service Crews¶
GET /service-crews¶
List service crews.
Query Parameters: serviceId, projectId, skip, limit
POST /service-crews¶
Create service crew with indirect costs.
Request Body:
{
"serviceId": "SVC-001",
"projectId": "PROJ-001",
"crewId": "CREW-001",
"labourMembers": [...],
"equipmentMembers": [...],
"indirectCosts": {
"labour": {
"traveling": {"percentage": 5, "amount": null},
"mobilization": {"percentage": 2, "amount": null}
},
"equipment": {
"markupProfitEquipment": {"percentage": 10, "amount": null}
}
}
}
POST /service-crews/bulk¶
Bulk create service crews.
GET /service-crews/{serviceCrewId}¶
Get service crew details.
PUT /service-crews/{serviceCrewId}¶
Update service crew.
DELETE /service-crews/{serviceCrewId}¶
Delete service crew.
DELETE /service-crews/service/{serviceId}/all¶
Delete all crews for a service.
Crews (Master Templates)¶
GET /crews¶
List crew templates.
POST /crews¶
Create crew template.
GET /crews/{crewId}¶
Get crew template.
PATCH /crews/{crewId}¶
Update crew template.
DELETE /crews/{crewId}¶
Delete crew template.
POST /crews/{crewId}/duplicate¶
Duplicate crew template.
Crew Trades¶
GET /crews/trades¶
List all trades.
POST /crews/trades¶
Create trade.
GET /crews/trades/{tradeCode}¶
Get trade.
PATCH /crews/trades/{tradeCode}¶
Update trade.
GET /crews/trades/{tradeCode}/dependencies¶
Check trade dependencies before deletion.
DELETE /crews/trades/{tradeCode}/cascade¶
Cascade delete trade and remove from crews.
Crew Members¶
GET /crews/members¶
List crew members (rate cards).
Query Parameters: country, region, province, tradeCode, year, quarter
POST /crews/members¶
Create crew member.
GET /crews/members/{memberId}¶
Get crew member.
PATCH /crews/members/{memberId}¶
Update crew member.
DELETE /crews/members/{memberId}¶
Delete crew member.
Equipment¶
GET /equipment¶
List equipment catalog.
POST /equipment¶
Create equipment.
GET /equipment/{equipmentCode}¶
Get equipment.
PUT /equipment/{equipmentCode}¶
Update equipment.
DELETE /equipment/{equipmentCode}¶
Delete equipment.
POST /equipment/import/async¶
Async equipment import.
Equipment Tags¶
Project-specific equipment pricing.
GET /equipment-tags¶
List equipment tags.
Query Parameters: projectId (required)
POST /equipment-tags¶
Create equipment tag.
GET /equipment-tags/{tagId}¶
Get equipment tag.
PUT /equipment-tags/{tagId}¶
Update equipment tag.
DELETE /equipment-tags/{tagId}¶
Delete equipment tag.
WBS (Work Breakdown Structure)¶
GET /wbs¶
List WBS items.
Query Parameters: scope (universal/project), projectId
POST /wbs¶
Create WBS.
GET /wbs/{wbsId}¶
Get WBS.
PUT /wbs/{wbsId}¶
Update WBS.
DELETE /wbs/{wbsId}¶
Delete WBS.
Subcontractors¶
GET /projects/{projectId}/subcontractors¶
List subcontractors for project.
POST /projects/{projectId}/subcontractors¶
Create subcontractor.
POST /projects/{projectId}/subcontractors/import¶
Batch import subcontractors.
GET /projects/{projectId}/subcontractors/{subcontractorId}¶
Get subcontractor.
PUT /projects/{projectId}/subcontractors/{subcontractorId}¶
Update subcontractor.
DELETE /projects/{projectId}/subcontractors/{subcontractorId}¶
Delete subcontractor.
API Keys¶
Requires Contributor role.
GET /api-keys¶
List API keys (hashed, shows metadata only).
POST /api-keys¶
Create API key.
Request Body:
Response (key shown only once):
{
"id": "key-123",
"key": "lco_abc123...",
"keyPrefix": "lco_abc1",
"name": "CI Pipeline Key",
"role": "Reader",
"expiresAt": "2025-12-31T23:59:59Z"
}
DELETE /api-keys/{keyId}¶
Revoke API key.
Status Codes¶
| Code | Description |
|---|---|
| 200 | Success (GET/PUT/PATCH) |
| 201 | Created (POST) |
| 204 | No Content (DELETE) |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden (insufficient role) |
| 404 | Not Found |
| 409 | Conflict |
| 422 | Validation Error |
| 429 | Rate Limited |
| 500 | Server Error |
MTO Disciplines¶
| Discipline | Description | Complexity |
|---|---|---|
electrical_equipment |
Transformers, switchgear | High |
mechanical_equipment |
Pumps, vessels | High |
bulk_electrical |
Cables, conduits | Medium |
instrumentation |
Sensors, analyzers | Medium |
piping |
Pipes, fittings, valves | Highest (140+ fields) |
civil |
Excavation, earthwork | Medium |
concrete |
Foundations, structures | Medium |
structural |
Steel frames | Medium |
architectural |
Finishes, partitions | Low |
Interactive Documentation¶
- Swagger UI:
/api/docs - ReDoc:
/api/redoc - OpenAPI Spec:
/api/openapi.json
Code Examples¶
JavaScript/TypeScript with Auth¶
import axios from 'axios';
import { PublicClientApplication } from '@azure/msal-browser';
const msalConfig = {
auth: { clientId: 'your-client-id', authority: 'https://login.microsoftonline.com/your-tenant' }
};
const msalInstance = new PublicClientApplication(msalConfig);
const api = axios.create({ baseURL: 'http://localhost:8000/api/v1' });
api.interceptors.request.use(async (config) => {
const account = msalInstance.getAllAccounts()[0];
if (account) {
const response = await msalInstance.acquireTokenSilent({
scopes: ['api://your-api-id/user_impersonation'],
account
});
config.headers.Authorization = `Bearer ${response.accessToken}`;
}
return config;
});
// Get projects
const { data } = await api.get('/projects', { params: { clientId: 'CLIENT-001' } });
API Key Authentication¶
const api = axios.create({
baseURL: 'http://localhost:8000/api/v1',
headers: { 'X-API-Key': 'lco_your-api-key-here' }
});
const { data } = await api.get('/projects');
Python with httpx¶
import httpx
headers = {"X-API-Key": "lco_your-api-key-here"}
async with httpx.AsyncClient(base_url="http://localhost:8000/api/v1", headers=headers) as client:
response = await client.get("/projects", params={"clientId": "CLIENT-001"})
projects = response.json()