API Documentation
Getting Started
Overview
The Log My Business API allows third-party applications to access and manage organization data including customers, projects, tasks, time entries, and mileage records. All API endpoints are scoped to an organization and require proper authentication.
Base URL
https://app.logmybusiness.com/api
Organization ID
All API endpoints require your Organization ID in the path. You can find it in two places:
- The Developer Portal displays your Organization ID at the top of the page
-
Authorization Code flow only: Call
GET /api/organization/my-organizationswith your access token to list all organizations the authenticated user belongs to
Quick Start
- Go to the Developer Portal and register a new application
- Choose Confidential for server-to-server access or Public for browser/mobile apps
- Select the API scopes your application needs
- Save your Client ID and Client Secret (shown only once)
- Use the credentials to obtain an access token
- Include the access token in the
Authorizationheader of your API requests
Two Authentication Flows
Client Credentials (Server-to-Server)
For backend services that need to access organization data without user interaction. The token acts on behalf of the organization, not a specific user.
Best for: cron jobs, data sync, reporting tools, backend integrations
Authorization Code + PKCE (User-Delegated)
For applications that need to act on behalf of a specific user. The user authorizes the application via a consent screen, and the token carries their identity and permissions.
Best for: web apps, mobile apps, third-party dashboards
Authentication
Client Credentials Flow
Exchange your client credentials for an access token. Requires a confidential client.
Request
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=api:customers:read api:projects:read
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
Authorization Code + PKCE Flow
For applications that need user-delegated access. Works with both public and confidential clients.
Step 1: Redirect user to authorize
GET /connect/authorize?
client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=https://your-app.com/callback
&scope=openid email profile api:customers:read
&code_challenge=YOUR_PKCE_CHALLENGE
&code_challenge_method=S256
The user will see a consent screen listing the requested permissions. After approving, they'll be
redirected to your redirect_uri with an authorization code.
Step 2: Exchange code for token
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=YOUR_CLIENT_ID
&code=AUTHORIZATION_CODE
&redirect_uri=https://your-app.com/callback
&code_verifier=YOUR_PKCE_VERIFIER
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"id_token": "eyJhbGciOiJSUzI1NiIs..."
}
Refreshing Tokens
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&client_id=YOUR_CLIENT_ID
&refresh_token=YOUR_REFRESH_TOKEN
Using Access Tokens
Include the access token in the Authorization header of every API request:
GET /api/customer/{organizationId}
Authorization: Bearer YOUR_ACCESS_TOKEN
Scopes
API Scopes
Scopes control what data your application can access. Request only the scopes you need.
| Scope | Description | Access Level |
|---|---|---|
api:customers:read | View customers and their details | Read |
api:customers:write | Create, update, and delete customers | Write |
api:projects:read | View projects and their details | Read |
api:projects:write | Create, update, and delete projects | Write |
api:tasks:read | View tasks and their details | Read |
api:tasks:write | Create, update, and delete tasks | Write |
api:time-entries:read | View time tracking entries | Read |
api:time-entries:write | Create, update, and delete time entries | Write |
api:mileage:read | View mileage tracking entries | Read |
api:mileage:write | Create, update, and delete mileage entries | Write |
api:members:read | View organization members and their roles | Read |
api:tags:read | View tags | Read |
api:tags:write | Create, update, and delete tags | Write |
api:invoices:read | View invoices, estimates, payments, and invoice settings | Read |
api:invoices:write | Create, update, send, and void invoices and estimates | Write |
api:expenses:read | View expenses and expense categories | Read |
api:expenses:write | Create, update, and delete expenses and categories | Write |
api:catalog:read | View products and services | Read |
api:catalog:write | Create, update, and delete products and services | Write |
openid, email, profile)
are also available for Authorization Code flow to access user identity information.
Endpoints
All endpoints require authentication and are scoped to an organization via the
{organizationId} path parameter. Abbreviated as {orgId} below.
Organization Members
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/organization/{orgId}/members | api:members:read | List organization members |
Response
[
{
"userId": "abc-123",
"email": "user@example.com",
"displayName": "Jane Smith",
"firstName": "Jane",
"lastName": "Smith",
"role": "Admin",
"joinedAt": "2025-06-15T10:30:00Z"
}
]
Customers
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/customer/{orgId} | api:customers:read | List all customers |
| GET | /api/customer/{orgId}/{id} | api:customers:read | Get customer details |
| POST | /api/customer/{orgId} | api:customers:write | Create a customer |
| PUT | /api/customer/{orgId}/{id} | api:customers:write | Update a customer |
| DELETE | /api/customer/{orgId}/{id} | api:customers:write | Delete a customer |
Create / Update Request Body
{
"name": "Acme Corp", // required
"companyName": "Acme Corporation",
"email": "contact@acme.com",
"phoneNumber": "555-0100",
"addressLine1": "123 Main St",
"city": "Springfield",
"stateProvince": "IL",
"postalCode": "62704",
"country": "US",
"customerType": "Business",
"status": "Active",
"notes": "Key account",
"creditLimit": 10000.00
}
Projects
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/project/{orgId} | api:projects:read | List all projects |
| GET | /api/project/{orgId}/{id} | api:projects:read | Get project details |
| POST | /api/project/{orgId} | api:projects:write | Create a project |
| PUT | /api/project/{orgId}/{id} | api:projects:write | Update a project |
| DELETE | /api/project/{orgId}/{id} | api:projects:write | Delete a project |
| GET | /api/project/{orgId}/{id}/users | api:projects:read | List project members |
| GET | /api/project/{orgId}/{id}/stats | api:projects:read | Get project statistics |
Create / Update Request Body
{
"name": "Website Redesign", // required
"customerId": 1, // optional, link to customer
"description": "Full site overhaul",
"status": "Active",
"projectCode": "WEB-001",
"hourlyRate": 150.00,
"isBillable": true,
"startDate": "2025-01-15",
"endDate": "2025-06-30",
"budgetAmount": 50000.00
}
Tasks
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/task/{orgId} | api:tasks:read | List all tasks |
| GET | /api/task/{orgId}/{id} | api:tasks:read | Get task details |
| GET | /api/task/{orgId}/kanban | api:tasks:read | Get kanban board view |
| POST | /api/task/{orgId} | api:tasks:write | Create a task |
| GET | /api/taskstatus/{projectId} | api:tasks:read | List a project's status columns (for statusId) |
| PUT | /api/task/{orgId}/{id} | api:tasks:write | Update a task |
| DELETE | /api/task/{orgId}/{id} | api:tasks:write | Delete a task |
| POST | /api/task/{orgId}/{id}/move | api:tasks:write | Change task status |
| POST | /api/task/{orgId}/{id}/assign | api:tasks:write | Assign users to task |
| POST | /api/task/{orgId}/{id}/tags/{tagId} | api:tasks:write | Add tag to task |
| DELETE | /api/task/{orgId}/{id}/tags/{tagId} | api:tasks:write | Remove tag from task |
| GET | /api/task/{orgId}/{id}/comments | api:tasks:read | List comments on a task |
| POST | /api/task/{orgId}/{id}/comments | api:tasks:write | Add a comment to a task |
| PUT | /api/task/{orgId}/{id}/comments/{commentId} | api:tasks:write | Update a comment (author only) |
| DELETE | /api/task/{orgId}/{id}/comments/{commentId} | api:tasks:write | Delete a comment (author, Admin, or Owner) |
Create Request Body
{
"title": "Design homepage mockup", // required
"projectId": 1, // required
"description": "Create initial wireframes",
"priority": 2, // 0=Low, 1=Medium, 2=High, 3=Critical
"dueDate": "2025-02-28",
"startDate": "2025-02-01",
"estimatedHours": 8.0,
"assigneeIds": ["user-id-1", "user-id-2"],
"tagIds": [1, 3]
}
Update Request Body (all fields optional)
{
"title": "Updated title",
"status": "InProgress", // Todo, InProgress, Review, Done
"priority": 3, // 0=Low, 1=Medium, 2=High, 3=Critical
"progress": 50,
"actualHours": 4.5
}
Comment Request Body (POST / PUT)
{
"comment": "Looks good — ready for review" // required, max 5000 chars
}
Comment Response
{
"id": 42,
"taskId": 5,
"userId": "abc-123",
"userName": "Jane Smith",
"profilePictureUrl": null,
"comment": "Looks good — ready for review",
"createdAt": "2026-05-05T14:22:00Z",
"updatedAt": null,
"isSystemGenerated": false
}
Tags
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/tag/{orgId} | api:tags:read | List all tags |
| GET | /api/tag/{orgId}/{id} | api:tags:read | Get tag details |
| POST | /api/tag/{orgId} | api:tags:write | Create a tag |
| PUT | /api/tag/{orgId}/{id} | api:tags:write | Update a tag |
| DELETE | /api/tag/{orgId}/{id} | api:tags:write | Delete a tag |
| GET | /api/tag/{orgId}/{id}/tasks | api:tags:read | List tasks with this tag |
Create / Update Request Body
{
"name": "Urgent", // required
"color": "#EF4444", // required, hex color
"description": "High priority items"
}
Time Entries
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/timeentry/{orgId}/week | api:time-entries:read | Get weekly time view |
| GET | /api/timeentry/{orgId}/day | api:time-entries:read | Get daily time view |
| GET | /api/timeentry/{orgId}/report | api:time-entries:read | Get time report |
| POST | /api/timeentry/{orgId} | api:time-entries:write | Create a time entry |
| PUT | /api/timeentry/{orgId}/{id} | api:time-entries:write | Update a time entry |
| DELETE | /api/timeentry/{orgId}/{id} | api:time-entries:write | Delete a time entry |
Create / Update Request Body
{
"projectId": 1, // required
"date": "2025-02-15", // required
"hours": 4.5, // required, 0.01-24
"categoryId": 2, // optional project category
"taskId": 5, // optional link to task
"description": "Frontend work",
"startTime": "09:00:00", // optional
"endTime": "13:30:00" // optional
}
Mileage
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/mileage/{orgId} | api:mileage:read | List mileage entries |
| GET | /api/mileage/{orgId}/{id} | api:mileage:read | Get mileage entry details |
| POST | /api/mileage/{orgId} | api:mileage:write | Create a mileage entry |
| PUT | /api/mileage/{orgId}/{id} | api:mileage:write | Update a mileage entry |
| DELETE | /api/mileage/{orgId}/{id} | api:mileage:write | Delete a mileage entry |
Create Request Body
{
"projectId": 1, // required
"date": "2025-02-15", // required
"totalMiles": 45.2, // required
"startMiles": 12500.0, // optional, odometer start
"endMiles": 12545.2, // optional, odometer end
"mileageRate": 0.67, // optional, override default
"description": "Client site visit",
"isBillable": true
}
Invoices & Estimates
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/invoice/{orgId} | api:invoices:read | List invoices and estimates |
| GET | /api/invoice/{orgId}/{id} | api:invoices:read | Get invoice details (lines, payments) |
| GET | /api/invoice/{orgId}/{id}/pdf | api:invoices:read | Download the invoice PDF |
| GET | /api/invoice/{orgId}/billable-items | api:invoices:read | Uninvoiced time/mileage/expense items |
| GET | /api/invoice/{orgId}/settings | api:invoices:read | Invoice settings (numbering, terms, tax) |
| POST | /api/invoice/{orgId} | api:invoices:write | Create an invoice or estimate |
| PUT | /api/invoice/{orgId}/{id} | api:invoices:write | Update a draft invoice |
| POST | /api/invoice/{orgId}/{id}/send | api:invoices:write | Send (and optionally email) an invoice |
| POST | /api/invoice/{orgId}/{id}/payment | api:invoices:write | Record a manual payment |
| POST | /api/invoice/{orgId}/{id}/void | api:invoices:write | Void an invoice |
| POST | /api/invoice/{orgId}/{id}/convert | api:invoices:write | Convert an estimate to an invoice |
| DELETE | /api/invoice/{orgId}/{id} | api:invoices:write | Delete a draft invoice |
Tax-rate management (/api/invoice/{orgId}/tax-rates) and CSV export
(/api/invoice/{orgId}/export) follow the same read/write scope split.
Recurring invoice schedules
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/recurring-invoice/{orgId} | api:invoices:read | List recurring schedules |
| POST | /api/recurring-invoice/{orgId}/from-invoice/{invoiceId} | api:invoices:write | Create a schedule from an invoice (body: cadence, interval, startDate, endDate?, autoSend) |
| POST | /api/recurring-invoice/{orgId}/{id}/toggle | api:invoices:write | Pause or resume a schedule |
| DELETE | /api/recurring-invoice/{orgId}/{id} | api:invoices:write | Delete a schedule (generated invoices are kept) |
Expenses
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/expense/{orgId} | api:expenses:read | List expenses (filter by date, project, task) |
| GET | /api/expense/{orgId}/{id} | api:expenses:read | Get expense details |
| POST | /api/expense/{orgId} | api:expenses:write | Create an expense |
| PUT | /api/expense/{orgId}/{id} | api:expenses:write | Update an expense (locked once invoiced) |
| DELETE | /api/expense/{orgId}/{id} | api:expenses:write | Delete an expense (locked once invoiced) |
| GET | /api/expensecategory/{orgId} | api:expenses:read | List expense categories |
| POST | /api/expensecategory/{orgId} | api:expenses:write | Create a category (Owner/Admin) |
Create Request Body
{
"projectId": 1, // required
"taskId": 42, // optional, link to a task
"date": "2025-02-15", // required
"categoryId": 3, // required
"description": "Parts for job", // required
"amount": 129.99, // required
"merchant": "Hardware Depot", // optional
"isBillable": true
}
Products & Services Catalog
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/catalogitem/{orgId} | api:catalog:read | List catalog items |
| GET | /api/catalogitem/{orgId}/{id} | api:catalog:read | Get catalog item details |
| POST | /api/catalogitem/{orgId} | api:catalog:write | Create a catalog item (Owner/Admin) |
| PUT | /api/catalogitem/{orgId}/{id} | api:catalog:write | Update a catalog item (Owner/Admin) |
| DELETE | /api/catalogitem/{orgId}/{id} | api:catalog:write | Delete a catalog item (Owner/Admin) |
Webhooks (outbound)
Webhooks push event notifications to your server the moment something changes in Log My Business — tasks, customers, invoices, and estimates — so you don't have to poll. Register an HTTPS endpoint in the Developer Portal → Webhooks section, choose the events you care about, and we'll POST a signed JSON payload to it on every matching event.
Event Types
| Event | Fires when |
|---|---|
task.created | A task is created |
task.updated | A task's fields, checklist, attachments, or order change |
task.moved | A task changes status (e.g. dragged to another Kanban column) |
task.assigned | Users are assigned to or unassigned from a task |
task.commented | A comment is added to a task |
task.deleted | A task is deleted |
customer.created | A customer is created |
customer.updated | A customer's details, tags, or custom fields change |
customer.deleted | A customer is deleted |
invoice.created | An invoice is created — manually, from an estimate, or by a recurring schedule |
invoice.sent | An invoice is sent to the customer |
invoice.paid | An invoice becomes fully paid — by an online payment or a manually recorded one |
invoice.overdue | An unpaid invoice passes its due date (checked about every 30 minutes) |
estimate.accepted | A customer signs and accepts an estimate on the public page |
estimate.declined | An estimate is declined — by the customer, or marked declined in the app |
ping | You press Send test event in the Developer Portal |
Delivery Format
Every delivery is an HTTP POST with a JSON body:
POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-LMB-Event: task.moved
X-LMB-Delivery: 3f8a1c9e6b2d4f709a8b1c2d3e4f5a6b
X-LMB-Timestamp: 1727683200
X-LMB-Signature: sha256=deadbeef...
{
"id": "3f8a1c9e6b2d4f709a8b1c2d3e4f5a6b",
"type": "task.moved",
"createdAt": "2026-09-30T08:00:00Z",
"organizationId": 42,
"data": {
"organizationId": 42,
"projectId": 7,
"taskId": 1234
}
}
The data object carries just the ids relevant to the event —
taskId for task events, customerId for customer events,
invoiceId / estimateId (plus customerId) for
invoicing events — alongside organizationId.
| Header | Description |
|---|---|
X-LMB-Event | The event type, same as type in the body |
X-LMB-Delivery | Unique id for this delivery — use it to de-duplicate retries |
X-LMB-Timestamp | Unix seconds when the delivery was signed |
X-LMB-Signature | sha256= + HMAC signature (see below) |
Verifying Signatures
Each subscription has a signing secret (shown once when you create it, and again only
if you rotate it). Verify every delivery before trusting it: compute
HMAC-SHA256(secret, "{timestamp}.{rawBody}") — the timestamp from the
X-LMB-Timestamp header, a literal dot, then the raw request body — and
compare it to the hex digest in X-LMB-Signature.
// Node.js example
const crypto = require("crypto");
function verify(req, secret) {
const ts = req.headers["x-lmb-timestamp"];
const sig = req.headers["x-lmb-signature"]; // "sha256=<hex>"
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(`${ts}.${req.rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - ts) < 300; // reject stale deliveries
}
Retries and Auto-Disable
- Respond with any
2xxstatus within 10 seconds to acknowledge a delivery. - Failed deliveries are retried twice (after roughly 5 and 30 seconds) — 3 attempts in total.
- After 10 consecutive failed events, the subscription is automatically disabled and the last failure reason is shown in the Developer Portal. Re-enabling it clears the strike count.
localhost during development). Webhook management requires a plan with API
access and an Owner/Admin role or the Developer Portal permission.
Errors
Error Handling
The API uses standard HTTP status codes to indicate success or failure.
Error Response Format
{
"message": "A human-readable error description"
}
HTTP Status Codes
| Code | Meaning | Description |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
400 | Bad Request | Invalid request body or parameters |
401 | Unauthorized | Missing or invalid access token |
403 | Forbidden | Token lacks required scope or organization access |
404 | Not Found | Resource does not exist |
500 | Server Error | Unexpected server error |
401 error, your access token may have expired.
Use the refresh token to obtain a new access token.