Skip to content

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):

  1. API Key - For programmatic access

    X-API-Key: lco_<32-character-token>
    

  2. Azure Entra ID JWT - For interactive users

    Authorization: Bearer <jwt-token>
    

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

Content-Type: application/json
Authorization: Bearer <token>
or
Content-Type: application/json
X-API-Key: lco_<key>

Response Format

All successful responses return JSON with camelCase field names.

Success Response (200 OK):

{
  "id": "PROJ-001",
  "projectName": "Sample Project",
  "createdAt": "2024-01-10T10:00:00Z"
}

Error Response (4xx/5xx):

{
  "detail": "Error message describing what went wrong"
}

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:

{
  "data": [...],
  "total": 100,
  "skip": 0,
  "limit": 20,
  "hasMore": true
}

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:

{
  "status": "healthy",
  "service": "LCO API",
  "version": "2.0.0",
  "database": "connected"
}


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:

{
  "projectId": "PROJ-001",
  "serviceId": "SVC-001",
  "items": [...]
}

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:

{
  "taskId": "task-123",
  "status": "queued"
}

GET /mto/import/status/{taskId}

Poll import status.

Response:

{
  "taskId": "task-123",
  "status": "running",
  "progress": {
    "current": 500,
    "total": 1000
  }
}

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:

{
  "name": "CI Pipeline Key",
  "role": "Reader",
  "expiresAt": "2025-12-31T23:59:59Z"
}

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()

cURL with API Key

curl -X GET "http://localhost:8000/api/v1/projects?clientId=CLIENT-001" \
  -H "X-API-Key: lco_your-api-key-here"