API Overview
The LawnLedger CRM API is a RESTful JSON API that allows you to integrate with your LawnLedger data programmatically. All endpoints are prefixed with /api/ and return JSON responses.
Base URL
https://api.lawnledgercrm.comFor local development:
http://localhost:3000Authentication
Most API requests use cookie-based session authentication - the same session your browser holds after signing in.
There is a second path: API keys, for server-to-server integrations that have no browser session. API keys require the apiAccess feature (Business plan). New API keys are coming soon: for now no one can create one, and keys that already exist keep working. The Zapier integration is coming soon too: it opens once Zapier has its own set of key permissions. The mobile app and the customer portal each authenticate differently again, with their own tokens.
API keys
Creating new API keys is paused for now. If your business already has keys, they keep working, and you can see and revoke them under Settings → Integrations → API keys. The rest of this section describes how a key behaves.
Each key carries its own permissions, picked when it was created - Read-only, or Custom from the permissions its creator holds - and the key is shown once. Send it as a bearer token:
curl https://api.lawnledgercrm.com/api/customers \
-H "Authorization: Bearer llk_live_..."A key reaches these endpoints, each with the permission named:
| Endpoint | Permission |
|---|---|
GET /api/customers, GET /api/customers/:id | customers.view |
POST /api/customers · PATCH /api/customers/:id · DELETE /api/customers/:id | customers.create · customers.edit · customers.delete |
GET /api/properties, GET /api/properties/:id | properties.view |
POST /api/properties · PUT/PATCH /api/properties/:id · DELETE /api/properties/:id | properties.create · properties.edit · properties.delete |
GET /api/jobs, GET /api/jobs/:id | jobs.view |
POST /api/jobs · PUT /api/jobs/:id · DELETE /api/jobs/:id | jobs.create · jobs.edit · jobs.delete |
GET /api/estimates, GET /api/estimates/:id | estimates.view |
POST /api/estimates · PUT /api/estimates/:id · DELETE /api/estimates/:id | estimates.create · estimates.edit · estimates.delete |
GET /api/invoices, GET /api/invoices/:id, GET /api/invoices/:id/payments | invoices.view |
POST /api/invoices · PUT /api/invoices/:id · DELETE /api/invoices/:id | invoices.create · invoices.edit · invoices.delete |
POST /api/invoices/:id/payments | invoices.record_payment |
GET /api/expenses, GET /api/expenses/:id · POST /api/expenses | expenses.view · expenses.create |
Anything else answers 403 with API_KEY_ROUTE_NOT_ALLOWED, and a listed endpoint without its permission answers 403 with API_KEY_SCOPE_MISSING (the required field names the permission). A key never does more than the person who created it can do today, and never anything an admin does - settings, your team, billing, integrations, or moving money.
Keys created before key permissions existed show as Legacy on the keys page. While new keys are paused, legacy keys keep working with no end date, because there is no way to create a replacement. When new keys open again, the creator of each legacy key is emailed before anything changes, with what the key has been calling and a link to a replacement.
Logging In
To authenticate, send a POST request to the auth endpoint:
curl -X POST https://api.lawnledgercrm.com/api/auth/sign-in/email \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "you@example.com",
"password": "your-password"
}'The -c cookies.txt flag saves the session cookie to a file. Use -b cookies.txt on subsequent requests to include it.
Authenticated Requests
curl https://api.lawnledgercrm.com/api/customers \
-b cookies.txtAll API requests (except auth endpoints) require authentication. Unauthenticated requests return a 401 Unauthorized response.
Response Format
All successful responses follow a consistent envelope format:
{
"data": { ... }
}The data field contains the response payload (an object, array, or primitive).
Error Responses
Errors return an error object with a message and code:
{
"error": {
"message": "Customer not found",
"code": "NOT_FOUND"
}
}HTTP status codes:
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | No Content (empty body) |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (not logged in) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 429 | Too Many Requests (rate limited) |
| 500 | Internal Server Error |
Multi-Tenancy
LawnLedger is a multi-tenant application. All data is scoped to your organization automatically based on your session. You do not need to pass an organization ID in most requests.
Rate Limiting
API requests are rate-limited to prevent abuse. If you exceed the rate limit, you will receive a 429 response. Wait and retry after the indicated period.
The API is designed for integration purposes. For high-volume data access, consider using the bulk export features available in the app settings.
Available Endpoints
| Resource | Base Path |
|---|---|
| Customers | /api/customers |
| Invoices | /api/invoices |
| Estimates | /api/estimates |
| Contracts | /api/contracts |
| Jobs | /api/jobs |
| Webhooks | /api/webhooks |
| Properties | /api/properties |
| Services | /api/services |
| Payments (Stripe intents, refunds, reconciliation) | /api/payments |
| Equipment | /api/equipment |
| Time Tracking | /api/crew-tracking |
| Winter Routes | /api/winter-routes |
| Dashboard | /api/dashboard |
| Settings | /api/settings |