This page shows you how to combine Command Alkon ticket data and Bitumio job data with your PlantDemand schedule inside a single Power BI report. Once the three feeds are wired together, your dashboard answers the question every plant manager actually asks: did the day go the way we planned, and if not, where did it slip?
Why merge ticket data with PlantDemand
PlantDemand is a planning system — it captures the orders, customers, materials, and projected tonnages that you intend to produce on a given day. Command Alkon is a ticketing and dispatch system — it captures what actually happened: which truck loaded what mix, when it left, when it returned, and how much material crossed the scale. Bitumio sits on the contractor side of the same conversation — what jobs the paving crew bid, won, and is currently scheduling.
Each system answers a different question. The interesting answers come from joining them.
- Plan vs actual tonnage — PlantDemand orders next to Command Alkon scale tickets, grouped by day and mix.
- Customer fill rate — what percentage of each customer’s planned tonnage actually shipped.
- Mix yield drift — variance between the design tonnage in PlantDemand and the loaded tonnage on the Command Alkon ticket.
- Crew-to-plant alignment — Bitumio’s scheduled paving days against the plant’s planned production days.
- Project-level profitability hints — Bitumio’s bid tonnage and crew hours next to the actual delivered tonnage from Command Alkon.
Command Alkon: what is exposed and how
Command Alkon’s modern integration layer is the CONNEX platform. Their developer portal — developer.commandalkon.com — documents the REST endpoints, OpenAPI specs, and webhook events for tickets and master data. Older COMMANDseries, Apex, Integra, and TrackIt deployments increasingly surface their data through the same CONNEX pipes.
Authentication is OAuth 2.0 client-credentials. Your Command Alkon representative provisions a client_id and client_secret for your tenant; you exchange those for a short-lived bearer token that goes in the Authorization header of every request.
The exact endpoint paths and field names depend on which CONNEX modules you are licensed for and which version is enabled in your tenant — always confirm the current spec in your developer portal before building against it. The fields you can typically expect on a ticket record include:
- Identification — ticket number, sequence number, status
- Plant and region — plant ID, plant name, region/branch
- Customer and project — customer ID and name, project ID and name, job site address
- Material — mix code, mix description, slump or other spec, additives
- Quantities — ordered, loaded, delivered (in tons or yards)
- Logistics and timing — truck ID, dispatcher, load time, departure, arrival on job, start pour, finish pour, return to plant
For real-time use cases, CONNEX webhooks push ticket events (“Truck Loaded”, “Arrived On Job”, “Return to Plant”) to a URL you control — useful if you want a Power BI streaming dataset or a near-real-time tile rather than a 15-minute scheduled refresh.
Bitumio: what is exposed and how
Bitumio is an asphalt paving estimating and management platform aimed at the contractor side of the business — bidding, job tracking, scheduling crews and equipment, customer CRM, and invoicing.
Bitumio does not currently publish an open developer portal. Practical ways to get Bitumio data into Power BI today:
- Zapier — Bitumio publishes a public Zapier integration. The most reliable Power BI pattern is to have a Zap write every new or updated Bitumio job into a SharePoint Online list, a Google Sheet, or a Dataverse table, and point Power BI at that table. This gives you a stable, refreshable surface for jobs, customers, and status changes.
- QuickBooks bridge — Bitumio syncs estimates and invoices into QuickBooks Online or Desktop. Power BI’s QuickBooks Online connector then gives you the financial side of every Bitumio job for free.
- Direct API access — Bitumio reportedly grants programmatic API access to enterprise customers on request. If you have it, treat it the same way as the Command Alkon block below: bearer or API-key auth, a periodic GET into a Power BI table.
The data fields you can expect from Bitumio (regardless of transport) include: bid / estimate ID, job ID, job status, customer name, scheduled start and end dates, estimated tons, crew assignment, equipment assignment, mix code, and project address.
The data model: how the three systems relate
The fact tables are the three transactional sources — PlantDemand orders, Command Alkon tickets, Bitumio jobs. The dimension tables are the keys you use to join them.
| Dimension | Why it matters | Notes |
|---|---|---|
| Date | Joins everything. PlantDemand deliveryOn = Command Alkon LoadTime date = Bitumio ScheduledStartDate. |
Use a Power BI date table marked as a date dimension; create relationships from each fact table’s date column to it. |
| Plant | Each system has its own plant identifier — PlantDemand plantId, Command Alkon plantId, Bitumio plantId. |
Build a small Plant lookup table with one row per real plant and the three system IDs as columns. |
| Customer | Names rarely match across systems (“Acme Paving Inc.” vs “Acme Paving” vs “ACME PAVING”). | Build a Customer crosswalk table with a canonical name and the three system IDs. |
| Mix / material | PlantDemand mix names and Command Alkon mix codes are often different conventions. | Build a Mix crosswalk table mapping PD material name → CA mix code → design tons-per-yard if applicable. |
| Project / job | Bitumio’s job ID is the contractor-side anchor; Command Alkon’s project ID is the producer-side anchor. | If you control both, populate Bitumio’s job number into the PlantDemand order extra-fields and into the Command Alkon project field so the join is one-to-one. |
In Power BI’s Model view, create the relationships like this:
PlantDemandOrders[DeliveryDate]→DimDate[Date](many-to-one, single)CommandAlkonTickets[DeliveryDate]→DimDate[Date](many-to-one, single)BitumioJobs[ScheduledStartDate]→DimDate[Date](many-to-one, single)PlantDemandOrders[PlantId]→DimPlant[PlantDemandPlantId]CommandAlkonTickets[PlantId]→DimPlant[CommandAlkonPlantId]BitumioJobs[PlantId]→DimPlant[PlantDemandPlantId](or its own column)- Repeat the same pattern for
DimCustomerandDimMix.
Power BI query 1 — Pull Command Alkon tickets
Open Home → Get data → Blank query → Advanced Editor in Power BI Desktop and paste this. Replace the three placeholder values at the top with your CONNEX tenant ID, your bearer token, and your preferred date range. (For a production report, store the OAuth token-exchange step in a separate query rather than pasting a long-lived bearer token in plain text.)
let
pCommandAlkon_BaseURL = "https://api.commandalkon.com",
pCommandAlkon_Tenant = "REPLACE_WITH_YOUR_CONNEX_TENANT_ID",
pAccessToken = "REPLACE_WITH_OAUTH_BEARER_TOKEN",
pDateFrom = Date.ToText(Date.AddDays(Date.From(DateTime.LocalNow()), -7), "yyyy-MM-dd"),
pDateTo = Date.ToText(Date.From(DateTime.LocalNow()), "yyyy-MM-dd"),
Source = Json.Document(Web.Contents(pCommandAlkon_BaseURL,
[
RelativePath = "/connex/v1/tickets",
Query = [
tenantId = pCommandAlkon_Tenant,
dateFrom = pDateFrom,
dateTo = pDateTo,
pageSize = "1000"
],
Headers = [
Authorization = "Bearer " & pAccessToken,
Accept = "application/json"
]
])),
items = Source[items],
asTable = Table.FromList(items, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
expanded = Table.ExpandRecordColumn(asTable, "Column1",
{"ticketNumber","plantId","plantName","customerId","customerName","projectId","projectName",
"mixCode","mixDescription","orderedQuantity","loadedQuantity","deliveredQuantity",
"truckId","dispatcherId","loadTime","departureTime","arrivalOnJob","startPour","finishPour","returnToPlant"},
{"TicketNumber","PlantId","PlantName","CustomerId","CustomerName","ProjectId","ProjectName",
"MixCode","MixDescription","OrderedQty","LoadedQty","DeliveredQty",
"TruckId","DispatcherId","LoadTime","DepartureTime","ArrivalOnJob","StartPour","FinishPour","ReturnToPlant"}),
typed = Table.TransformColumnTypes(expanded, {
{"LoadTime", type datetime}, {"DepartureTime", type datetime}, {"ArrivalOnJob", type datetime},
{"StartPour", type datetime}, {"FinishPour", type datetime}, {"ReturnToPlant", type datetime},
{"OrderedQty", type number}, {"LoadedQty", type number}, {"DeliveredQty", type number}
}),
withDate = Table.AddColumn(typed, "DeliveryDate", each Date.From([LoadTime]), type date)
in
withDate
The exact RelativePath, query parameters, and field names depend on your CONNEX tenant configuration — confirm them against your developer portal before going to production.
Power BI query 2 — Pull Bitumio jobs (via SharePoint)
This example assumes you have a Zap that writes each new or updated Bitumio job into a SharePoint Online list named BitumioJobs. Adjust the SharePoint site URL and list name for your environment. The same pattern works against a Google Sheet, an Excel file in OneDrive, or a Dataverse table.
// Bitumio does not publish an open API. The two practical paths are:
// (1) Have Zapier write each new Bitumio Job into a SharePoint list / Google Sheet
// and connect Power BI to that list.
// (2) Use Bitumio's QuickBooks integration and connect Power BI to QuickBooks Online.
// The example below pulls from a SharePoint Online list called "BitumioJobs" that
// a Zap populates whenever a Bitumio job changes status.
let
Source = SharePoint.Tables(
"https://YOURTENANT.sharepoint.com/sites/Operations",
[ApiVersion = 15]),
BitumioJobs = Source{[Title = "BitumioJobs"]}[Items],
cleaned = Table.SelectColumns(BitumioJobs, {
"BitumioJobId", "JobName", "CustomerName", "JobStatus",
"EstimatedTons", "ScheduledStartDate", "ScheduledEndDate",
"PlantId", "CrewId", "MixCode"
}),
typed = Table.TransformColumnTypes(cleaned, {
{"EstimatedTons", type number},
{"ScheduledStartDate", type date},
{"ScheduledEndDate", type date}
})
in
typed
Power BI query 3 — Plan vs actual by day and mix
This is the headline report. It joins the PlantDemand plan against the Command Alkon actuals, computes a tonnage variance, and computes a customer fill-rate. Paste this as a third blank query after the PlantDemand and Command Alkon queries are loaded — it depends on a query named CommandAlkonTickets.
let
plantId = "14",
pdYesterday = Date.ToText(Date.AddDays(Date.From(DateTime.LocalNow()), -1), "yyyy-MM-dd"),
PlantDemandKey = "REPLACE_WITH_YOUR_PLANTDEMAND_API_KEY",
PlanRaw = Json.Document(Web.Contents("https://plantdemand.com/",
[
RelativePath = "graphql",
Query = [
query = "{ orders(plantId: " & plantId & ", dateFrom: """ & pdYesterday & """) { items { deliveryOn quantity material { name } customer { name } } } }"
],
Headers = [SERVER_API_KEY = PlantDemandKey, Accept = "application/json"]
])),
PlanItems = PlanRaw[data][orders][items],
PlanTable = Table.FromRecords(List.Transform(PlanItems, each [
DeliveryDate = Date.FromText(_[deliveryOn]),
PlannedTons = _[quantity],
Material = _[material][name],
Customer = _[customer][name]
])),
PlanByDayMix = Table.Group(PlanTable, {"DeliveryDate","Material"},
{{"PlannedTons", each List.Sum([PlannedTons]), type number}}),
// CommandAlkonTickets is the table created by the Command Alkon query above
ActualByDayMix = Table.Group(CommandAlkonTickets, {"DeliveryDate","MixDescription"},
{{"ActualTons", each List.Sum([LoadedQty]), type number}}),
Joined = Table.NestedJoin(
PlanByDayMix, {"DeliveryDate","Material"},
ActualByDayMix, {"DeliveryDate","MixDescription"},
"Actual", JoinKind.FullOuter),
expanded = Table.ExpandTableColumn(Joined, "Actual", {"ActualTons"}, {"ActualTons"}),
withVar = Table.AddColumn(expanded, "VarianceTons",
each (if [ActualTons] = null then 0 else [ActualTons]) - (if [PlannedTons] = null then 0 else [PlannedTons]),
type number),
withFill = Table.AddColumn(withVar, "FillRatePct",
each if [PlannedTons] = null or [PlannedTons] = 0 then null
else (if [ActualTons] = null then 0 else [ActualTons]) / [PlannedTons],
type number)
in
withFill
Power BI query 4 — Truck cycle time and project throughput
This one runs entirely off the Command Alkon ticket table and produces an operations-team dashboard: average cycle minutes, average haul minutes, distinct truck count, and total tons delivered, grouped by day, plant, and project.
let
Tickets = CommandAlkonTickets,
withCycle = Table.AddColumn(Tickets, "CycleMinutes",
each if [LoadTime] = null or [ReturnToPlant] = null
then null
else Duration.TotalMinutes([ReturnToPlant] - [LoadTime]),
type number),
withHaul = Table.AddColumn(withCycle, "HaulMinutes",
each if [DepartureTime] = null or [ArrivalOnJob] = null
then null
else Duration.TotalMinutes([ArrivalOnJob] - [DepartureTime]),
type number),
grouped = Table.Group(withHaul, {"DeliveryDate","PlantName","ProjectName"},
{
{"AvgCycleMin", each List.Average([CycleMinutes]), type number},
{"AvgHaulMin", each List.Average([HaulMinutes]), type number},
{"TruckCount", each List.Count(List.Distinct([TruckId])), Int64.Type},
{"TotalTons", each List.Sum([LoadedQty]), type number}
})
in
grouped
Five questions a merged report can finally answer
- “How close did we get to plan yesterday?” — visual: PlannedTons vs ActualTons by mix, with VarianceTons as a line. Driven by query 3.
- “Which customer is consistently underfilled?” — visual: average FillRatePct by customer over the trailing 30 days. Driven by query 3 with the Customer crosswalk.
- “Where is the bottleneck on long-cycle days?” — visual: AvgCycleMin and AvgHaulMin by project, scatter against TotalTons. Driven by query 4.
- “Are paving crews and the plant scheduled on the same days?” — visual: Bitumio scheduled jobs (estimated tons) overlaid on PlantDemand planned production by day, by plant. Driven by Bitumio + PlantDemand queries via DimDate and DimPlant.
- “Did the bid tonnage match what we ultimately delivered?” — visual: Bitumio EstimatedTons vs sum of Command Alkon LoadedQty by Bitumio JobId. Requires the Project / job crosswalk to be populated upstream.
Want this conversational instead of dashboard-shaped?
Once your tables are joined in Power BI you can publish them to the Power BI service and ask Q&A questions in natural language. For the planning side, the PlantDemand MCP server lets Claude, ChatGPT, Cursor, and Copilot Studio query your live PlantDemand schedule directly — no dashboard required, just plain English. The MCP server uses the same API key you already created in the API token walkthrough.
Combining the dashboard view (history, drill-down, comparison) with the conversational view (live questions, ad-hoc analysis) is what an AI-augmented plant management workflow actually looks like in 2026.
Security reminders
- Use a separate, least-privilege API key for each system and each integration. Power BI gets read-only keys.
- Set expiration dates on every API key and rotate before they hit. See the MCP security overview for the underlying principles.
- Do not paste bearer tokens or API keys in plain text inside production Power BI queries. Use a parameter, a Power BI dataflow with credentials stored in the service, or a key vault accessible to the gateway.
- Never commit a Power BI .pbix file containing secrets to source control.
What this page is and is not
This is a blueprint built from publicly available information about Command Alkon’s CONNEX platform and Bitumio’s published feature set. The Power BI snippets are templates — exact CONNEX endpoints, query parameters, and field shapes will vary by tenant and module licensing. Always validate against your live developer portal and a test Power BI report before pointing leadership at the dashboard.
Frequently Asked Questions
Does Command Alkon have a public API?
Yes. Command Alkon publishes a developer portal at developer.commandalkon.com covering the CONNEX platform. CONNEX exposes REST endpoints and webhooks for ticket events, master data, and dispatch. Access is gated — you authenticate with OAuth 2.0 client credentials provisioned by your Command Alkon representative, and the available scopes depend on the products you are licensed for (COMMANDseries, Apex, Integra, TrackIt).
Does Bitumio have a public API?
Bitumio does not publish an open developer portal at the time of writing. They offer a public Zapier integration and a direct QuickBooks Online and QuickBooks Desktop integration, and they grant programmatic API access to enterprise customers on request. If you need Bitumio data in Power BI today, the most reliable paths are Zapier-fed tables, the QuickBooks bridge, or scheduled CSV exports — confirm current options with your Bitumio account manager.
What is the most useful piece of data to merge with PlantDemand?
Truck-level ticket actuals from Command Alkon. PlantDemand tells you what was planned and ordered; Command Alkon tickets tell you what actually loaded out. Joining them on plant + day + mix gives you a plan-vs-actual view, which is the foundation of every meaningful operational metric — tonnage variance, on-time delivery, mix yield, and customer fill rate.
How do I match customers between systems if names are spelled differently?
Build a small “customer crosswalk” table that maps each system’s customer ID to a single canonical customer name. Maintain it in Power BI as a manual lookup (or in a SharePoint list / Excel workbook the BI report refreshes from). One row per real-world customer, with PlantDemand customer ID, Command Alkon customer ID, and Bitumio customer ID columns, joined to all three fact tables on the relevant key.
This guide is part of PlantDemand’s asphalt scheduling software hub for asphalt plant operations, scheduling, and sales management.