Skip to content
Browse documentation
API REFERENCEv1

Millbase API

Build with your organization’s live data.

Bring projects, inventory, labor, and costs into your own apps and workflows with a straightforward, read-only REST API.

BASE URLhttps://app.millbase.ca/api/v1

Your first request

You’ll need a Millbase account and administrator access to the organization you want to connect.

  1. Create an API key

    In Millbase, open Settings → API and integrations → API keys. Name your integration, select Warehouse and projects, choose an expiry, then generate a key.

    Open API settings
  2. Save your key

    Copy it when it’s created; the full key is shown only once. Replace the placeholder below in your terminal. Store production keys in your server’s secret manager.

    Terminal
    export MILLBASE_API_KEY='YOUR_MILLBASE_API_KEY'
  3. Read your projects

    Run this on your server or in a terminal. Node.js 22+ and Python 3 examples use built-in libraries.

    curl --fail-with-body --get 'https://app.millbase.ca/api/v1/data/projects' \
      -H "Authorization: Bearer $MILLBASE_API_KEY" \
      --data-urlencode 'fields=id,name,status' \
      --data-urlencode 'limit=10'

What comes back

A JSON object with your rows and pagination details. This is a fictional example; your response contains your organization’s records. An empty data array means no records matched.

JSON
{
  "data": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "name": "Example kitchen",
      "status": "active"
    }
  ],
  "pagination": {
    "limit": 10,
    "offset": 0,
    "nextOffset": null
  }
}

Authentication

Send your key in the Authorization header with every request: Authorization: Bearer YOUR_MILLBASE_API_KEY.

Use one key per integration, with only the permissions it needs. Keys can expire after 30, 90, or 365 days. Revoke a key in API settings to stop access, then create a replacement when rotating credentials.

Keep keys in server-side environment variables or a secret manager. Never put them in browser code, public repositories, URLs, or chat messages.

Endpoint reference

Start with the catalog to see exactly which datasets and fields your key can access.

cURL
curl --fail-with-body 'https://app.millbase.ca/api/v1/resources' \
  -H "Authorization: Bearer $MILLBASE_API_KEY"
GET/resourcesDiscover allowed datasets and fields
GET/data/{resource}Read rows with an optional equality filter
POST/queryRead rows with multiple filters

All paths below are relative to the base URL. This reference lists every public dataset; your key’s permissions determine which ones you can read. Expand a row’s fields to see its allowed columns.

25 / 25 endpoints

EndpointRequired permissionFields
GET/data/organizationoperations:read
14 fields
idnameslugtimezonecurrencylanguagelogo_urlaccent_colorweek_starts_ondaily_overtime_minutesweekly_overtime_minutesovertime_multipliercreated_atupdated_at
GET/data/projectsoperations:read
13 fields
idnamenumberclientdescriptionstatusprogress_stageparent_project_idtag_idsstart_datelabor_hours_estimatecreated_atupdated_at
GET/data/project-estimatesfinancials:read
8 fields
idnameestimated_delivery_costestimated_installation_costestimated_labor_costestimated_materials_costestimated_others_costupdated_at
GET/data/project-clientspeople:read
9 fields
project_idcontact_nameaddressemailphonewebsitenotesfollow_up_dateupdated_at
GET/data/project-categoriesoperations:read
5 fields
project_idcodelabelis_customsort_order
GET/data/project-tagsoperations:read
4 fields
idnamecolorcreated_at
GET/data/materialsoperations:read
10 fields
idcodenameuniton_handminimum_stockactiveimage_urlcreated_atupdated_at
GET/data/material-costsfinancials:read
5 fields
idcodenameunit_costupdated_at
GET/data/material-lotsfinancials:read
7 fields
idmaterial_idquantity_receivedquantity_remainingunit_costcreated_atupdated_at
GET/data/project-materialsfinancials:read
11 fields
idproject_idmaterial_idcustom_namequantityremaining_quantityunitunit_costtotal_costcreated_atupdated_at
GET/data/inventory-activityfinancials:read
14 fields
idmaterial_idproject_idproject_nametask_log_idactivity_typequantityquantity_deltabalance_afterunit_costfrom_locationto_locationnotecreated_at
GET/data/workstationsoperations:read
6 fields
idcodenamelocationcreated_atupdated_at
GET/data/workerspeople:read
11 fields
idnameemailroleworker_typearchivedstation_codeactive_log_idavatar_urlcreated_atupdated_at
GET/data/worker-compensationpeople:readfinancials:read
4 fields
idnamehourly_rateupdated_at
GET/data/memberspeople:read
7 fields
idnameemailroleavatar_urlcreated_atupdated_at
GET/data/task-logsoperations:read
11 fields
idproject_idworker_idworkstation_idcategory_codestart_atend_atsecondsstatuscreated_atupdated_at
GET/data/task-segmentsoperations:read
6 fields
idtask_log_idstart_atend_atsecondscreated_at
GET/data/task-materialsoperations:read
7 fields
task_log_idmaterial_idproject_material_idcodenamequantityunit
GET/data/historical-labor-costsfinancials:read
4 fields
task_log_idlabor_costsourcecaptured_at
GET/data/shiftsfinancials:read
8 fields
idstart_atend_atstatustotal_secondstotal_earningscreated_atupdated_at
GET/data/time-adjustmentspeople:read
7 fields
idworker_iddatereasonsecondscreated_atupdated_at
GET/data/project-filesoperations:read
9 fields
idproject_idfolder_idnamecontent_typesizestatuscreated_atupdated_at
GET/data/project-foldersoperations:read
5 fields
idproject_idnamecreated_atupdated_at
GET/data/designsdesigns:read
7 fields
idproject_idnamesharedversioncreated_atupdated_at
GET/data/design-assetsdesigns:read
6 fields
iddesign_idnamesizestatuscreated_at

Generated from Millbase’s public OpenAPI schema. View live OpenAPI · Download this reference

Filters & queries

For a simple GET, use fields, limit, and offset. Add filter=id and value=YOUR_RECORD_UUID to find one record. URL-encode parameter values.

Use POST /query for several filters or date ranges. For example, read task logs for a project. Replace PROJECT_UUID with an ID from your projects response.

curl --fail-with-body 'https://app.millbase.ca/api/v1/query' \
  -H "Authorization: Bearer $MILLBASE_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
  "resource": "task-logs",
  "fields": [
    "id",
    "project_id",
    "worker_id",
    "seconds",
    "status"
  ],
  "filters": [
    {
      "field": "project_id",
      "op": "eq",
      "value": "PROJECT_UUID"
    }
  ],
  "limit": 50,
  "offset": 0
}'
ParameterHow to use it
resourceRequired. A dataset name from the catalog, such as projects.
fieldsOptional. Select 1–40 allowed fields. Omit to return all public fields for the resource.
filtersUp to 10 filters. All must match (AND). Supported operators: eq (equals), gte (at least), lte (at most). Use ISO 8601 timestamps for date ranges.
limit1–100 rows. Default: 50.
offset0–1,000,000. Default: 0. Use nextOffset from the previous page.

Queries accept only the catalog’s named fields. Wildcards, joins, SQL, arbitrary tables, and organization overrides are not supported.

Pagination

Read pagination.nextOffset and pass it as offset in the next request. Stop when it is null. Keep the same resource, fields, and filters across pages.

Node.js
import { setTimeout } from 'node:timers/promises';

let offset = 0;
do {
  const response = await fetch('https://app.millbase.ca/api/v1/query', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MILLBASE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ resource: 'materials', limit: 100, offset }),
    signal: AbortSignal.timeout(30_000),
  });
  if (!response.ok) throw new Error(await response.text());
  const { data, pagination } = await response.json();
  console.log(data);
  offset = pagination.nextOffset;
  if (offset !== null) await setTimeout(1100);
} while (offset !== null);

Each page reads current stored data without response caching. Records may change while you paginate; this is not a frozen export. For ongoing work, elapsed seconds reflect the last saved value, so use start and end timestamps when calculating running durations.

Permissions & privacy

API and MCP access is available on Free, Pro, and Enterprise. An organization administrator chooses the permissions for each integration.

PermissionWhat it covers
operations:readWarehouse and projectsProjects, stock levels, stations, and task logs.
people:readWorkers and client contactsWorker details, members, and project contacts.
financials:readCosts and financial dataProject estimates, material costs, shifts, and historical labor costs.
designs:readDesigns and design assetsDesign and asset metadata.

Worker compensation requires both people:read and financials:read. The endpoint reference lists every required permission; all listed permissions must be granted.

Limits & errors

60requests / minute / key or connection
120requests / minute / organization
100,000requests / UTC month / organization

API and MCP share these organization limits. Creating more keys does not increase them. Authenticated discovery, initialization, and invalid queries also count. Requests are limited to 16 KB and responses to 1 MB.

HTTPWhat to do
400Check the resource, field names, filter types, and pagination values.
401Check the Bearer header. Your key may be invalid, expired, or revoked.
403Check your key’s permissions and administrator access to the organization.
413Send a smaller request, select fewer fields, or reduce the page size.
429Wait for the Retry-After interval before retrying. Check shared organization usage.
503The data is temporarily unavailable. Retry with a delay.

Errors return error.code and error.message. For 429 responses, respect the Retry-After header; use backoff with jitter for temporary failures. The X-Monthly-Remaining header reports remaining organization requests.

Connecting an agent instead of building an app? Follow the MCP guide.

KEEP BUILDINGConnect an AI agent with MCP