The API is read-only in version 1. It returns your own company's data and nothing else. Every key and webhook is managed by your company's administrators, and fieldGRID turns API access on for your company on request.
Getting started
- Ask fieldGRID to turn on API access for your company: contact@fieldgrid.cloud.
- An administrator opens People & access → API in the portal and creates a key, choosing what it may read.
- Copy the key when it is shown. It is shown once; fieldGRID stores only a scrambled form of it.
- Call the API from your system:
curl -H "Authorization: Bearer fgk.YOUR-KEY" https://fieldgrid.cloud/api/v1/jobsAPI keys
Send the key in the Authorization header as Bearer <key> on every request. A key looks like fgk.<company>.<key id>.<secret>; keep it as secret as a password, in your system's secret store, never in a browser or app that customers can open.
Each key holds one or more scopes. An administrator can only give a key access they have themselves.
| Scope | Reads |
|---|---|
jobs:read | Every job: title, dates, people, status, custom fields |
people:read | Active members: name, email, role |
forms:read | Forms and every submission's answers |
Revoke a key in the portal at any time; it stops working at once. A company can have up to 20 active keys.
Responses and errors
Successful responses are { "data": ..., "nextCursor": ... }. Lists return up to 100 items per page (?limit=, default 50), ordered by id; pass the nextCursor you received as ?cursor= to get the next page. nextCursor is null on the last page. Times are ISO 8601 in UTC.
Errors are { "error": { "code": "...", "message": "..." } }:
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_key, revoked_key | The key is missing, wrong or revoked |
| 403 | missing_scope | The key does not have the scope this endpoint needs |
| 403 | api_not_enabled, company_unavailable, module_not_licensed | API access is off, the subscription is not active, or the plan does not include this data |
| 404 | not_found | No record with that id in your company |
| 429 | rate_limited | Too many requests; wait for the seconds in Retry-After |
| 503 | unavailable | Temporary; retry shortly |
Jobs
GET /api/v1/jobs lists every job; GET /api/v1/jobs/{jobId} returns one. Scope jobs:read.
{
"data": [{
"id": "Xq3...", "title": "Replace valve, Unit 4",
"statusLabelId": "done", "completed": true, "completedAt": "2026-10-12T14:05:00.000Z",
"dates": ["2026-10-12"], "assignedTo": ["uid1", "uid2"], "teamId": null,
"customerId": "c-17", "jobTypeId": "maintenance", "jobTypeVersion": 3,
"customFields": { "poNumber": "PO-2291" }, "notes": "Access via rear gate",
"createdAt": "2026-10-10T08:00:00.000Z", "createdBy": "uid9", "updatedAt": "2026-10-12T14:05:00.000Z"
}],
"nextCursor": "Xq3..."
}People
GET /api/v1/people lists your company's active members: id, displayName, email, status, roleId, roleName, jobTitle, teamId. Scope people:read. The ids match the ones in a job's assignedTo.
Forms and submissions
GET /api/v1/forms lists your forms with their fields (id, type, label). GET /api/v1/forms/{formId}/submissions lists a form's submissions: id, formId, formRevision, jobId (when filled for a job), submittedBy, submittedAt, createdAt, answers (keyed by field id) and files (each file's name and type). Scope forms:read. The files themselves stay in fieldGRID.
Webhooks
Add a webhook in People & access → API: an https address on a public host name and the events you want. fieldGRID shows a signing secret (whsec_…) once; store it with your receiver.
| Event | When | data |
|---|---|---|
job.created | A job is created | The job, as GET /api/v1/jobs/{id} |
job.updated | Something the API shows about a job changes | The job |
job.completed | A job is completed (sent with job.updated) | The job |
form.submitted | A form is submitted | The submission |
ping | You press “Send test” | A test message |
Each delivery is a POST with a JSON body and these headers:
POST /your-endpoint
Content-Type: application/json
FieldGRID-Event: job.completed
FieldGRID-Delivery: 9fK2... (unique per delivery; use it to ignore duplicates)
FieldGRID-Signature: t=1760277900,v1=5d41402abc4b2a76b9719d911017c592...
{ "id": "9fK2...", "event": "job.completed", "createdAt": "2026-10-12T14:05:00.000Z",
"companyId": "...", "data": { ...the job... } }Answer with any 2xx status within 10 seconds. Anything else, or no answer, is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, then marked failed. Redirects are not followed. The last deliveries and their results are listed under each webhook in the portal.
Verifying a webhook
Check every delivery before trusting it: compute the HMAC-SHA256 of <t>.<raw body> with your signing secret and compare it with v1, and reject a t more than 5 minutes old. Use the body exactly as received, before parsing it.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received (a string), before any JSON parsing.
export function verifyFieldgrid(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(",").map((part) => part.split("=")));
const sentAt = Number(parts.t);
if (!sentAt || Math.abs(Date.now() / 1000 - sentAt) > 300) return false; // older than 5 minutes
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 || "", "hex");
return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, "hex"));
}Python
import hashlib, hmac, time
def verify_fieldgrid(raw_body: bytes, signature_header: str, secret: str) -> bool:
parts = dict(part.split("=", 1) for part in signature_header.split(","))
sent_at = int(parts.get("t", "0"))
if abs(time.time() - sent_at) > 300: # older than 5 minutes
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))No-code tools (Zapier, Make, Power Automate) can receive webhooks on their own https address; check the signature with a code step where the tool offers one.
Limits
- 120 requests a minute per key; up to 100 items per page.
- Up to 20 active keys and 10 webhooks per company.
- Version 1 reads only. Creating or changing records is planned; tell us what you need at contact@fieldgrid.cloud.
- fieldGRID's processing of your company's data is governed by the Data Processing Agreement.
