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: senddurationMinutesas a multiple of 15 (0–720, e.g.465= 7h 45m) — preferred — or the legacy decimalhours(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, andhoursderived 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 isnullfor an archived project, whose link no longer resolves. - Read and update your business profile — name, website, the timezone
your dates are recorded in, the
geoStampingEnabledtoggle for location stamping, andhoursMode— how crew record their hours:diary(the default) orcheck_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/tenantreturns a signedlogoUrlto 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 —
entryDateon a diary entry, and thefrom/tosubmission filters. These are plainyyyy-mm-dddays 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
- Authentication — get your tenant API key and send your first request.
- Connect an AI assistant (MCP) — connect Claude, ChatGPT, or another MCP client to your tenant.
- API reference — every endpoint, request, and response, generated from the OpenAPI contract.