Skip to content

Generate Change Orders from a Spreadsheet

Use this recipe when a project manager hands you a spreadsheet of approved scope changes and you need each row to land as a draft change order in BuildWorkPro. The idempotency key derived from each row id makes the script safely re-runnable when the network blips halfway through.

  1. Read the spreadsheet.

    import xlsx from 'node-xlsx';
    const sheet = xlsx.parse('./scope-changes-2026-04.xlsx')[0];
    const headers = sheet.data[0];
    const rows = sheet.data
    .slice(1)
    .map((r) => Object.fromEntries(headers.map((h, i) => [h, r[i]])));
    // rows = [{ rowId: "R-001", projectId: 14, title: "Concrete delta", amount: 4200, ... }]
  2. POST one change order per row.

    The endpoint is POST /api/v1/change-orders. Required fields are projectId and title; everything else is optional or populated server-side.

    for (const row of rows) {
    const r = await fetch('https://app.buildworkpro.com/api/v1/change-orders', {
    method: 'POST',
    headers: {
    Authorization: `Bearer ${process.env.BWP_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': `co-import-2026-04-${row.rowId}`,
    },
    body: JSON.stringify({
    projectId: row.projectId,
    title: row.title,
    description: row.notes ?? null,
    reason: row.reason ?? null,
    totalAmount: String(row.amount),
    }),
    });
    if (!r.ok) {
    const body = await r.json();
    console.error(`row ${row.rowId} failed:`, body);
    }
    }

    Note that totalAmount is a string in the API to avoid floating-point precision loss on currency.

  3. Submit the drafts in a second pass (optional).

    New change orders default to status: "draft". To advance them through the workflow, call POST /api/v1/change-orders/{id}/submit (and later /approve or /reject once a decision is made). Doing this in a second pass means a partial first run still leaves the spreadsheet ids reconcilable.

  4. Reconcile.

    Re-list with ?projectId=... to verify the count matches the spreadsheet. Any rows that 4xx’d above are still in your error log; fix them in the source and re-run with the same script — idempotency keys make the successful rows no-op.