Authentication
Get your tenant API key from Settings and send it with every request.
The public API authenticates with a single per-tenant API key. The key is tenant-scoped and acts with Admin-equivalent, tenant-wide access — it only ever sees and touches its own tenant's data.
Get your key
There are two ways to get a key:
-
Fastest — sign up over the API. A single unauthenticated
POST /signupreturns a working key immediately, no dashboard needed. See Getting started → Sign up. -
From the dashboard — if you already have a FOREMAN account:
- Sign in to FOREMAN as an Admin.
- Open Settings → API & integrations.
- Your tenant's API key is shown there, always visible with a Copy button. Copy it whenever you need it (it isn't a one-time reveal).
Only Admins can see this page. The key is stored encrypted at rest.
Regenerating
Regenerate rotates the key. After you confirm, the old key stops working on the very next request and the new key takes its place. Regenerate is also the recovery path if a key is ever exposed.
Send the key
Send the key in the x-api-key request header:
curl https://app.foremanapp.com.au/api/v1/projects \
-H "x-api-key: YOUR_API_KEY"An Authorization: Bearer YOUR_API_KEY header is also accepted for clients that
default to it.
Verify a key resolves to your tenant with the diagnostic endpoint:
curl https://app.foremanapp.com.au/api/v1/whoami \
-H "x-api-key: YOUR_API_KEY"
# { "tenantId": "…", "role": "admin" }Response shape
List endpoints wrap results in { "data": [...], "nextCursor": ... }. A
single resource is returned as the bare object (no envelope). Errors always
use { "error": {...} } (see below). All requests and responses are JSON; send
Content-Type: application/json on writes.
Pagination
List endpoints page with ?limit= and an opaque ?cursor=:
limit— page size, 1–200 (default 50).cursor— pass back thenextCursora previous page returned. Omit it for the first page. Don't construct cursors yourself; they're opaque.
{
"data": [
/* … */
],
"nextCursor": "…or null on the last page"
}Keep fetching, passing the previous nextCursor as ?cursor=, until
nextCursor comes back null — that's the last page.
Limits & retries
- Rate limits — only
POST /signupis rate-limited in v1 (per IP →429). Authenticated endpoints have no per-key rate limit today, but don't assume unlimited throughput; back off on any429. - Idempotency — there are no idempotency keys in v1, so a retried
POSTcan create a duplicate. Guard retries on the client side.
Errors
Every failed request returns a JSON envelope:
{
"error": {
"code": "validation",
"message": "One or more inputs are invalid.",
"fields": { "name": "Enter a project name." }
}
}| Status | error.code | When |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
403 | read_only / project_cap | Subscription cancelled (writes disabled, reads still work), or a plan limit is hit (e.g. the Free-plan project cap — the message carries an upgrade link). |
404 | not_found | No such resource in your tenant. |
405 | method_not_allowed | Writing a read-only resource (create/edit it in the app instead). |
409 | conflict | Conflict with the resource's state (e.g. deleting a template that has submissions). |
422 | validation | Validation failed — see error.fields for the per-field detail. |
429 | rate_limited | Too many requests — currently only on POST /signup (per-IP cap). |
See the API reference for the request and response shape of every endpoint.