Getting started

Connect to your FOREMAN tenant over HTTP or from an AI assistant.

The FOREMAN public API lets you work with your tenant's data — your company's isolated FOREMAN workspace — outside the web app: wiring up integrations, exporting to other systems, or (the headline use case) connecting FOREMAN to an AI assistant to ask plain-language questions over your site data.

Everything runs over one tenant-scoped HTTP surface. The same operations are also exposed as MCP tools so an assistant like Claude or ChatGPT can call them directly.

What you can do in v1

Write:

  • Create, read, update, and delete Projects, Tasks, and Form templates — building form templates over the API (or via an AI assistant) is expected to be the primary way templates get made.
  • Manage crew — assign and unassign users to projects, and set a site manager.
  • Manage users — create, update, revoke, reinstate, and reset passwords.
  • Log diary entries on behalf of a crew member, and upload documents, diary photos, and standalone project photos (a photo attached straight to a project, no diary entry) — register the file, then upload it to the secure link we return. When attaching a photo you can pass { path, originalFilename } in place of a bare path to set the name it downloads as; a bare path still works and downloads under a generated name. Diary hours entered by hand move in 15-minute steps with a 12-hour maximum: send durationMinutes as a multiple of 15 (0–720, e.g. 465 = 7h 45m) — preferred — or the legacy decimal hours (0–12, rounded to the nearest minute, in 0.25-hour steps). Entries created by a check-out can hold any whole minute (e.g. 518 = 8h 38m): exact minutes are the rule for DERIVED hours, not typed ones. Entries read back with both fields: durationMinutes, and hours derived from it (minutes ÷ 60).
  • Edit induction content for a project, and read its induction link — the URL visitors scan at the gate. Every project carries an inductionUrl, so you can put a site's induction link straight into your own onboarding email, app, or signage without reconstructing it. It is null for an archived project, whose link no longer resolves.
  • Read and update your business profile — name, website, the timezone your dates are recorded in, the geoStampingEnabled toggle for location stamping, and hoursMode — how crew record their hours: diary (the default) or check_in (ADR 0012).
  • Set your business logo — upload a PNG/JPG/SVG (up to 5 MB) via the same request-a-link-then-PUT flow as documents; GET /api/v1/tenant returns a signed logoUrl to display it.
  • Generate document download links and an upgrade Checkout link.
  • Organise documents into folders — list and create the folders of a project's OHS or Project Information library, and file a document into one when you register it. Folders are one level deep and organisational only: putting a document in a folder never changes who can see it.

A project's address is geocoded into coordinates when you create it or change the address, so give as precise an address as you can. If it can't be geocoded the project is still created, with null coordinates — nothing fails. A project also carries a free-text standardHours label (e.g. 07:00–15:00) shown on the crew's check-in card — display only, never enforced. A diary entry may carry an optional location reading (latitude, longitude, locationAccuracyM); on read, each entry exposes a computed siteLocation verdict (on_site / off_site / missing) against the project's coordinates, or null when the tenant has location stamping switched off. Entries produced by a check-in/out shift have no submit-time reading of their own — their siteLocation follows the shift (the check-out event's verdict when there was one, the check-in's otherwise), and the shift's own facts ride alongside: checkInAt, checkOutAt, checkInLocation, checkOutLocation (each null when its event never happened — an auto-closed shift has no check-out verdict), closedAutomaticallyAt/closedAutomaticallyReason, and the site's siteTimezone for rendering the times.

Reading a diary closes what should already have closed. Any diary read — GET /api/v1/diary-entries, the list_diary_entries MCP tool, or a browse in the app — first closes the expired open shifts its scope reaches (12 hours after check-in, ADR 0012), so the list you get back is the truth rather than what was last written. An entry the sweep created carries origin: "auto_closed", createdBy: null, durationMinutes: null and hours: null — hours missing, which is not the same as 0, and counts as zero in every total until someone sets them — with closedAutomaticallyReason telling you why: 12h, or site_archived when the site was archived (that one has real hours, always under 12). An hourly database job closes the same shifts whether anyone reads or not, so a forgotten shift cannot wait forever for someone to look at it. Writing never closes a shift: PATCH and DELETE on these endpoints change only what you send.

Diary entries say who logged what. Every entry carries origin — manual, check_out, auto_closed, or logged_for — plus createdBy/createdByName (who logged it) beside userId/crewMemberName (whose hours they are). A create that names a crew member via userId is a delegation: the entry lands origin: "logged_for" and the app shows "Logged by [you] for [crew member]" — the same record a Site Manager makes with the For picker in the app. Rows also carry derived marks: no_check_in, and possible_duplicate on both entries when two same-day entries for one crew member plausibly cover a single shift. Neither mark is stored; both are computed at read.

Read (for aggregation): projects, tasks, users, diary entries, project photos (diary + standalone), form submissions, the induction register, and document metadata — the records an assistant reads to answer questions like "how many hours did Jamie log on Hawthorn this fortnight?".

Register entries carry userId — the uuid of the FOREMAN account that signed, or null when the signature came in anonymously at the site's public induction link. A linked entry settles that site's induction for the account: crew are asked to sign a site's induction before their first check-in there and never again for a site they've signed. The register is read-only over the API — signing happens in the app, at the public induction link or the crew's combined sign-and-check-in tap.

Filling in and submitting forms, and the public visitor-induction flow, stay in the app for now.

Dates and your timezone

FOREMAN separates two kinds of time, and the difference matters when you send dates:

  • Calendar dates — entryDate on a diary entry, and the from/to submission filters. These are plain yyyy-mm-dd days with no time or zone, and they are interpreted in your business's timezone, not UTC.
  • Timestamps — createdAt, submittedAt, doneAt, uploadedAt. These are absolute instants in ISO-8601 UTC.

Your timezone is on GET /api/v1/whoami (and GET /api/v1/tenant). Read it before sending a calendar date: at 7am in Sydney it is already "tomorrow" relative to UTC, so a date computed from a UTC clock would be the wrong day — and a diary entry dated ahead of your business's today is rejected as a future date.

Base URL and versioning

All endpoints live under /api/v1 on your FOREMAN deployment. /v1 is fixed for this version; any breaking change ships under a new version prefix.

Sign up

You don't need the dashboard to start. A single unauthenticated call creates an active tenant and returns a working API key immediately — no browser, no waiting on a verification email:

curl -X POST https://app.foremanapp.com.au/api/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"me@example.com","businessName":"Acme Builders"}'
# { "apiKey": "…use it now", "tenantId": "…", "message": "…" }

A code-capable AI assistant can do this for you and wire the key straight into its tools — see Connect an AI assistant. We email a link to set a dashboard password and confirm your email, but it gates nothing.

If the email is already registered, you get the same message with an empty apiKey — we don't reveal whether an account exists, so grab your key from the dashboard instead. Signup is also rate-limited per IP (429 if you retry too often).

Next steps

  1. Authentication — get your tenant API key and send your first request.
  2. Connect an AI assistant (MCP) — connect Claude, ChatGPT, or another MCP client to your tenant.
  3. API reference — every endpoint, request, and response, generated from the OpenAPI contract.

On this page