Integration-first: why DirtFleet leads with the API surface
Most fleet apps treat the public API as an afterthought. We treated it as the spine. Here's why every feature ships with REST endpoints + signed webhooks before the in-app UI is final.
Most fleet management apps treat the public API as an afterthought. They ship the in-app UI for two years, then bolt on a REST surface when an enterprise customer demands it. The API ends up half-finished, with weird endpoints that leak the internal data model. We did it backwards: every feature in DirtFleet ships with REST endpoints + signed webhooks before the in-app UI is final. Here's why.
The reasoning
Three observations from talking to fleets:
- Construction fleets already run 4–6 vendors. Estimating (Procore), accounting (QuickBooks), telematics (Samsara), dispatch (custom Excel), payroll (ADP). Adding a 7th vendor is a non-starter unless the integration is real.
- Most CMMS-style apps assume the world starts and ends inside their UI. That's never been true and it's less true every year — customers want data flowing in and out at API speed.
- The same APIs that integrate with QuickBooks also serve our own internal tools (admin dashboard, automated tests, customer success queries). If the API isn't good enough for us, it's not good enough for customers.
What “integration-first” means concretely
For every domain table — Asset, HoursLog, RepairLog, WorkOrder, Tool, Project, Incident, Flag — there are three things in the codebase before any UI work happens:
- The Prisma model with proper indexes, cascade behavior, and tenant scoping (every row carries organizationId).
- Lib repository functions with cross-tenant guards and consistent return shapes ({ ok: true, ... } | { ok: false, error: ... }).
- REST endpoints at
/api/v1/...with named scopes + idempotency keys + cursor pagination.
The in-app pages are then built ON TOP of the same lib functions, not parallel implementations. When a UI bug surfaces a deeper data issue, fixing the lib function fixes both the UI and the API at once.
Webhooks are part of the API, not a separate product
Pull APIs answer “tell me about state X” questions. Push (webhook) APIs answer “tell me when X happens.” For fleets the second is often more valuable — “ping me when a RED flag fires” routes through their existing Slack/Teams workflow without anyone polling our REST endpoint every 30 seconds.
Every meaningful event in DirtFleet emits a webhook from the same write path that updates the database. The dispatcher cron signs each delivery with HMAC-SHA256 + a timestamp window; receivers verify in 5 lines of code. Failed deliveries exponentially-back-off-retry up to 6 attempts then mark ABANDONED — visible in the customer's settings UI for debugging.
What we don't do
- GraphQL. The data shapes are simple enough that REST + cursor pagination beats GraphQL's complexity tax. Reconsider when integrators tell us they want it.
- Public Prisma client. Tempting to expose the raw Prisma model — saves marketing time. Bad call: it locks the public surface to internal naming, makes schema refactors API-breaking, and leaks tenant fields.
- Versioned-by-feature URLs. Stuck with
/api/v1/for the major; additive changes never bump version. New major only on breaking changes, with a 6-month deprecation window.
The ROI
Three months in, we already have a customer who built a custom dispatch dashboard against /api/v1/assets and webhooks for flag.created + workorder.completed. Total integration cost on their side: ~6 hours of work over a weekend. Without the public API, that customer would have left for a competitor that has one.
→ REST API reference · → Integrations directory · → Start free trial