The GIS API publishes your company's fieldGRID assets (poles, valves, cables, ducts and every other asset type you defined) as an OGC API Features service under https://fieldgrid.cloud/api/v1/gis/ogc. Every asset comes with its geometry, its status and label, the fields of its asset type and how many photos it has. GIS webhooks tell your system when assets change.
Works with OGC API Features clients such as QGIS and GDAL. Tested with the ArcGIS Maps SDK engine (.NET and JavaScript); ArcGIS Pro and the ArcGIS Online Map Viewer have not yet been validated by fieldGRID.
What the GIS API is
- OGC API Features Part 1 (core, GeoJSON, OpenAPI 3.0), read-only:
GETandHEADonly. Nothing can be created, changed or deleted through it. - Coordinates are WGS 84 longitude / latitude (
CRS84), as GeoJSON requires; your GIS reprojects on the fly. - One collection per asset type. Points, lines, polygons and their multi-part forms.
- The data is your company's own and nothing else, through the same access rules as fieldGRID itself: a key sees exactly the assets the person who created it sees.
- Photos are listed and downloaded through the API with the same key, never through a public or signed link.
The rest of the fieldGRID API (jobs, invoices, stock and more) is on the overview; the GIS API uses the same keys, the same per-company switch and the same webhooks.
Getting access
- Ask fieldGRID to turn on API access and the GIS API for your company: contact@fieldgrid.cloud. Both are off by default; turning API access off later stops every key and webhook of the company at once.
- In People & access → API, create a key with the scope “Read GIS data” (
gis:read). To create it, a person must be able to manage members, and to view and export GIS data for the whole company. When your company requires two-step sign-in, they must have it set up. A company can hold 2 active keys with “Read GIS data” (of its 20 keys in all). - Copy the key when it is shown. It is shown once.
- Add the server address
https://fieldgrid.cloud/api/v1/gis/ogcto your GIS client with the key as the password (see QGIS, GDAL, ArcGIS). In fieldGRID, Connect from QGIS on the GIS map shows the address, your company's limits and a connection test.
The key acts as its creator. Assets and their attributes and geometry, as the key's creator sees them. If the creator's view is limited to some areas, so is the key's. On every request fieldGRID checks that the creator still has the access above; a creator who loses it, leaves the company or (where two-step sign-in is required) no longer has a second step set up stops the key within a minute (403 key_owner_not_allowed), and the key list shows it as Stopped. A revoked key stops at once.
Authentication
Send the key on every request, in the Authorization header, in one of two forms:
- Basic: any user name, the full key as the password. This is what desktop GIS clients send.
- Bearer:
Authorization: Bearer fgk.<company>.<key id>.<secret>, as for the rest of the API.
Never put the key in the address. A request with a query parameter named key, api_key, apikey, token or access_token, or with anything that looks like a key (fgk.) in any parameter, is refused with 400 key_in_url and never read: addresses end up in logs, proxies and shared project files. In a desktop client this also means: no “custom request parameters” for the key. Every answer, the landing page included, needs a valid key; without one the answer is 401 with WWW-Authenticate: Basic, so clients ask for the credentials.
# Bearer
curl -H "Authorization: Bearer $FIELDGRID_GIS_KEY" "https://fieldgrid.cloud/api/v1/gis/ogc/collections"
# Basic: any user name, the key as the password
curl -u "fieldgrid:$FIELDGRID_GIS_KEY" "https://fieldgrid.cloud/api/v1/gis/ogc/collections/<collectionId>/items?limit=100&bbox=-6.30,53.33,-6.22,53.36"Endpoints
All under https://fieldgrid.cloud/api/v1/gis/ogc. POST, PUT, PATCH and DELETE answer 405 method_not_allowed. Every answer is Cache-Control: no-store. Links in the answers are absolute; follow them instead of building addresses.
| Endpoint | Methods | Returns |
|---|---|---|
/api/v1/gis/ogc | GET, HEAD | Links to the conformance classes, the OpenAPI document and the collections. |
/api/v1/gis/ogc/conformance | GET, HEAD | The OGC classes the API implements: Part 1 core, GeoJSON and OpenAPI 3.0. |
/api/v1/gis/ogc/api | GET, HEAD | The OpenAPI 3.0 description of every route, its parameters, limits and errors. |
/api/v1/gis/ogc/collections | GET, HEAD | The asset types the key can see, one collection each (plus the per-kind collections of a mixed type). |
/api/v1/gis/ogc/collections/{collectionId} | GET, HEAD | One asset type: title, geometry kinds, CRS and links. |
/api/v1/gis/ogc/collections/{collectionId}/queryables | GET, HEAD | A JSON Schema of the properties you can filter items on. |
/api/v1/gis/ogc/collections/{collectionId}/items | GET, HEAD | A GeoJSON FeatureCollection, one page of assets. |
/api/v1/gis/ogc/collections/{collectionId}/items/{featureId} | GET, HEAD | One asset as a GeoJSON Feature, with a link to each of its photos. |
/api/v1/gis/ogc/collections/{collectionId}/items/{featureId}/photos | GET, HEAD | The asset's photos, newest first, at most 200. |
/api/v1/gis/ogc/collections/{collectionId}/items/{featureId}/photos/{photoId} | GET, HEAD | The photo's bytes, streamed by fieldGRID. |
The OpenAPI document at /api/v1/gis/ogc/api describes every parameter and answer for your tools; like everything else it needs the key.
Collections
Each asset type the key can see is one collection: its id is the asset type's id and its title the type's name. Archived types are not listed. geometryKinds says which geometry kinds the type allows (empty: any kind). At most 500 collections are listed per company, ordered by name.
Asset types with several geometry kinds. QGIS and ArcGIS need one geometry type per layer. A type that declares two or three kinds (for example points and lines) is therefore listed twice over: once as <Type> (all geometries) for clients that accept mixed layers (GDAL, FME), and once per kind as {typeId}~point, ~line and ~polygon, titled (points), (lines), (polygons), each with only the assets of that kind (multi-part geometries count as their kind). In QGIS and ArcGIS, add the per-kind collections. A type with one kind, or with no declared kinds, is listed once; to get one collection per kind, declare the type's geometry kinds in its settings in fieldGRID.
{
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections",
"rel": "self",
"type": "application/json",
"title": "Asset types"
}
],
"collections": [
{
"id": "5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"title": "Poles",
"itemType": "feature",
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84"
],
"geometryKinds": [
"Point"
],
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"rel": "self",
"type": "application/json",
"title": "Poles"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items",
"rel": "items",
"type": "application/geo+json",
"title": "Items of this asset type"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/queryables",
"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
"type": "application/schema+json",
"title": "Queryable properties"
}
]
},
{
"id": "6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d",
"title": "Ducts (all geometries)",
"description": "Every geometry kind of this asset type in one collection. In QGIS or ArcGIS add the per-kind collections (points, lines, polygons) instead: they need one geometry type per layer.",
"itemType": "feature",
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84"
],
"geometryKinds": [
"Point",
"LineString"
],
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d",
"rel": "self",
"type": "application/json",
"title": "Ducts (all geometries)"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d/items",
"rel": "items",
"type": "application/geo+json",
"title": "Items of this asset type"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d/queryables",
"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
"type": "application/schema+json",
"title": "Queryable properties"
}
]
},
{
"id": "6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~point",
"title": "Ducts (points)",
"itemType": "feature",
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84"
],
"geometryKinds": [
"Point"
],
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~point",
"rel": "self",
"type": "application/json",
"title": "Ducts (points)"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~point/items",
"rel": "items",
"type": "application/geo+json",
"title": "Items of this asset type"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~point/queryables",
"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
"type": "application/schema+json",
"title": "Queryable properties"
}
]
},
{
"id": "6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~line",
"title": "Ducts (lines)",
"itemType": "feature",
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84"
],
"geometryKinds": [
"LineString"
],
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~line",
"rel": "self",
"type": "application/json",
"title": "Ducts (lines)"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~line/items",
"rel": "items",
"type": "application/geo+json",
"title": "Items of this asset type"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/6c1d0f2b-3a5e-4d9f-8b8c-2e3f4a5b6c7d~line/queryables",
"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
"type": "application/schema+json",
"title": "Queryable properties"
}
]
}
]
}Items, paging and filters
GET /collections/{collectionId}/items answers a GeoJSON FeatureCollection, ordered by asset id. Each asset is a Feature with its id, its geometry and properties: typeId, status, label, revision, createdAt, updatedAt, attributes (the fields of the asset type, only those its current definition has), photoCount, latestPhotoAt and photosPageUrl. Never who created or changed an asset, capture details or internal ids. Deleted assets never appear in items (webhooks tell you about deletes).
Paging. Up to 1,000 assets a page (default 100; a page also stops early before it passes 8 MB). When there is more, the answer carries a next link: follow it until there is none. Paging is by asset id, so an asset that already existed never appears twice or goes missing while you page, even when assets change meanwhile. There is no total count (numberMatched); numberReturned is the size of the page.
| Parameter | Meaning |
|---|---|
limit | Assets per page, 1 to 1,000 (default 100). A larger value is lowered to 1,000. |
bbox | Four numbers in WGS 84 longitude / latitude: minimum longitude, minimum latitude, maximum longitude, maximum latitude. Assets that intersect it. A box across the antimeridian (minimum longitude above the maximum) works. |
datetime | The time of an asset's LAST CHANGE: an instant, or an interval a/b, ../b or a/.. (RFC 3339 date-times with a time zone, for example 2026-10-16T00:00:00Z). |
cursor | Do not build it: follow the next link of the previous page, which carries it. |
f | json or geojson; both answer GeoJSON. |
crs | Only http://www.opengis.net/def/crs/OGC/1.3/CRS84 (the default) is accepted. |
bbox-crs | The same: only CRS84. |
status | Assets with exactly this status. |
<field>=<value> | Equality on up to 3 fields of the asset type at once: text and choice fields exactly, numbers exactly, yes / no fields as true or false. The queryables route lists the fields you can use. |
A parameter that is not valid, or not in this list and not a queryable field, is refused with 400 invalid_parameter (with details.parameter), never ignored. Coordinates in bbox are always longitude first. datetime filters on the last change, so ?datetime=2026-10-01T00:00:00Z/.. answers every asset changed since then: a simple way to keep another system in step.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"geometry": {
"type": "Point",
"coordinates": [
-6.26031,
53.349805
]
},
"properties": {
"typeId": "5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"status": "In service",
"label": "P-1042",
"revision": 3,
"createdAt": "2026-10-01T08:12:00.000Z",
"updatedAt": "2026-10-14T15:40:21.000Z",
"attributes": {
"material": "Steel",
"height_m": 9.5,
"inspected": true
},
"photoCount": 2,
"latestPhotoAt": "2026-10-14T15:39:02.000Z",
"photosPageUrl": "https://fieldgrid.cloud/manager/gis?feature=8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b&photos=1&company=your-company-id"
}
}
],
"numberReturned": 1,
"timeStamp": "2026-10-20T09:00:00.000Z",
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items?limit=1",
"rel": "self",
"type": "application/geo+json",
"title": "This page"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"rel": "collection",
"type": "application/json",
"title": "Poles"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items?limit=1&cursor=eyJ2IjoxLCJ0IjoiNWIwYyJ9",
"rel": "next",
"type": "application/geo+json",
"title": "Next page"
}
]
}Queryables
/collections/{collectionId}/queryables is a JSON Schema of the properties you can filter on: status and the type's text, choice, number and yes / no fields.
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/queryables",
"type": "object",
"title": "Poles",
"properties": {
"status": {
"title": "Status",
"type": "string"
},
"material": {
"title": "Material",
"type": "string",
"enum": [
"Wood",
"Steel"
]
},
"height_m": {
"title": "Height (m)",
"type": "number"
},
"inspected": {
"title": "Inspected",
"type": "boolean"
}
},
"additionalProperties": false
}One item
/collections/{collectionId}/items/{featureId} answers one asset, with an enclosure link for each of its photos and a related link to its photo list. Another company's id, an asset outside the key's view, a deleted asset and a malformed id all answer the same 404 not_found.
{
"type": "Feature",
"id": "8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"geometry": {
"type": "Point",
"coordinates": [
-6.26031,
53.349805
]
},
"properties": {
"typeId": "5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"status": "In service",
"label": "P-1042",
"revision": 3,
"createdAt": "2026-10-01T08:12:00.000Z",
"updatedAt": "2026-10-14T15:40:21.000Z",
"attributes": {
"material": "Steel",
"height_m": 9.5,
"inspected": true
},
"photoCount": 2,
"latestPhotoAt": "2026-10-14T15:39:02.000Z",
"photosPageUrl": "https://fieldgrid.cloud/manager/gis?feature=8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b&photos=1&company=your-company-id"
},
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"rel": "self",
"type": "application/geo+json"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"rel": "collection",
"type": "application/json",
"title": "Poles"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b/photos/9f0e1d2c-3b4a-4596-8877-665544332211",
"rel": "enclosure",
"type": "image/jpeg",
"title": "IMG_2041.jpg"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b/photos",
"rel": "related",
"type": "application/json",
"title": "Photos of this asset"
}
]
}Photos
photoCountandlatestPhotoAton every asset say whether it has photos, so you only ask when there are some..../items/{featureId}/photoslists them, newest first (at most 200): id, file name, type, size, caption, time andhref. Never who took them..../photos/{photoId}answers the bytes, streamed by fieldGRID with the same key:Content-Typeis the image type (JPEG, PNG, WebP, HEIC / HEIF),Content-Disposition: inlinewith the file name, no caching.HEADanswers the headers only.- No signed or public links. A photo address works only with the key in the header, so it is useless to anyone who finds it in a log or a project file.
photosPageUrlopens the asset's photos in fieldGRID after sign-in; the link holds no key and the person sees only what their own access allows. Use it where a client cannot send the key, such as a map tip or a popup image.
{
"photos": [
{
"id": "9f0e1d2c-3b4a-4596-8877-665544332211",
"fileName": "IMG_2041.jpg",
"contentType": "image/jpeg",
"sizeBytes": 2481152,
"caption": "Base of the pole",
"createdAt": "2026-10-14T15:39:02.000Z",
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b/photos/9f0e1d2c-3b4a-4596-8877-665544332211"
}
],
"links": [
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b/photos",
"rel": "self",
"type": "application/json",
"title": "Photos of this asset"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"rel": "item",
"type": "application/geo+json",
"title": "The asset"
},
{
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b/photos/9f0e1d2c-3b4a-4596-8877-665544332211",
"rel": "enclosure",
"type": "image/jpeg",
"title": "IMG_2041.jpg"
}
]
}Limits and availability
- Per key: 5 requests a second (bursts of 10). A request over it waits up to 2 seconds for its turn before it is refused, because QGIS and GDAL do not retry on their own.
- Per company: 300 requests a minute for all its keys together, and a monthly number of GIS API requests for your company, which the GIS map's Connect from QGIS panel shows.
- Pages: up to 1,000 assets and 8 MB; 500 collections.
- Photos: 60 photo requests a minute per key, 2 GiB of photo downloads a day per company (UTC day), 2 downloads at once per key; photos up to 25 MB.
- Above a limit:
429 rate_limited(this second or minute) or429 quota_exceeded(the month's requests, the day's photo downloads), always withRetry-Afterin seconds: wait that long, then carry on.
Availability. The GIS API answers while the GIS database is running. Outside those hours it answers 503 unavailable with Retry-After: 300; try again later. A request that would take too long (a very large area with a large page) answers 503 busy with a short Retry-After and the hint to use a smaller bbox or limit; a partial page is never returned.
Browsers (CORS)
Desktop clients and servers need nothing here. A web map that calls the API from the user's browser may read the answers when its page is served from https://<name>.arcgis.com (ArcGIS Online) or from one of your company's ArcGIS Enterprise origins: exact https://host[:port] addresses, up to 10, which someone who manages members sets in the GIS map's Connect from QGIS panel. Every other origin cannot read the answers.
- The page sends the key in the
Authorizationheader itself (for example with an ArcGIS Maps SDK request interceptor). Never cookies or credentials mode:Access-Control-Allow-Credentialsis never sent, and never*. - The preflight (
OPTIONS) needs no key and reveals nothing; it allowsGETandHEADwith theAuthorizationheader. Answers carryVary: Origin, Authorizationand exposeLink, Retry-After, Content-Disposition. - Anyone who can open a page that holds a key can read the key. Use a separate key for it, only on pages for your own staff, and revoke it when the page goes.
Errors
Errors use the same envelope as the rest of the API, { "error": { "code": "...", "message": "..." } }, with details.parameter on invalid_parameter. Branch on code; the message is for people. 429 and 503 always carry Retry-After.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_parameter | A parameter is not valid or not known (details.parameter names it). An unknown parameter is refused, never ignored, so a typo never returns everything. |
| 400 | bad_cursor | The cursor is not from a next link of the same query. |
| 400 | key_in_url | A key, or something that looks like one, is in the address. Send it only in the Authorization header. |
| 401 | invalid_key, revoked_key | No key, a wrong key or a revoked key. The answer carries a Basic challenge, so desktop clients ask for the credentials. |
| 403 | missing_scope | The key does not have "Read GIS data" (gis:read). |
| 403 | api_not_enabled, company_unavailable, module_not_licensed | API access is off for your company, its subscription is not active, or the GIS API is not turned on for it. |
| 403 | key_owner_not_allowed | The person who created the key can no longer read GIS data. Someone who can should create a new key. |
| 404 | not_found | Nothing with that id in what the key can see. The same answer for another company's id, an asset outside the key's view, a deleted asset and a malformed id. |
| 405 | method_not_allowed | The API is read-only: only GET and HEAD (Allow: GET, HEAD). |
| 429 | rate_limited | Too many requests for this key or your company, or too many photos a minute. Wait for the seconds in Retry-After. |
| 429 | quota_exceeded | Your company has used its GIS API requests for the month, or its photo downloads for the day. Retry-After says when it resets. |
| 500 | internal | Something failed on our side. Retry later; tell us if it persists. |
| 503 | unavailable | The GIS database is not running. Retry after the seconds in Retry-After (300). |
| 503 | busy | The API is busy, or the key already has two photo downloads running. Retry after Retry-After (a few seconds); a smaller bbox or limit helps. |
{
"error": {
"code": "invalid_parameter",
"message": "bbox must be 4 numbers: minLon,minLat,maxLon,maxLat (CRS84).",
"details": {
"parameter": "bbox"
}
}
}GIS webhooks
GIS events reach your https address as signed JSON POSTs, exactly like every other webhook (same body { id, event, createdAt, companyId, data }, signature, retries and duplicates rules). What is special about them:
| Event | When | data |
|---|---|---|
gis.feature.created | An asset is created or restored, inside the webhook creator's view. | The asset as GET .../items/{featureId} returns it, plus a self link. |
gis.feature.updated | An asset changes and is still inside the creator's view. | The asset as it is NOW (not the change), plus a self link. |
gis.feature.deleted | An asset is deleted (reason deleted), or a change moved it out of the creator's view (reason out_of_scope). | { id, reason }: the asset's id only. |
gis.features.changed | Many assets changed at once: an import or a bulk edit (one summary per asset type), and every asset change of the month above 10,000 deliveries (one summary per asset type and hour). | { group, source (import, bulk or coalesced), typeId, counts, ids, leftIds, more, itemsHint }: counts and ids only of what the creator can see, at most 500 ids per list. |
gis.change_request.created | A change request is created, inside the creator's view. | { id, status, previousStatus, createdAt, updatedAt }: never its title, note or author. |
gis.change_request.status_changed | A change request moves to another status. | The same fields, with the status it came from. |
- Who may add one. The same person as for a key: able to manage members, and to view and export GIS data for the whole company, with two-step sign-in set up where your company requires it. The change-request events also need someone who sees every change request (who may review, or create, change requests for the whole company).
- The webhook acts as its creator. It receives only what the creator can see: an asset outside the creator's view sends nothing, and a change that moves an asset out of it arrives as
gis.feature.deletedwithreason: "out_of_scope"and the id only. If the creator loses the access above, the webhook stops (shown as Stopped) until it is recreated or the access returns. - Checked again before every attempt. Before each attempt, the first one included, fieldGRID checks that API access and the GIS API are still on, the creator, and the asset's place in the creator's view again. Refused: the delivery is marked failed and not retried. When the check itself cannot run (for example while the GIS database is not running), the delivery waits without counting as a failed attempt and is sent when it can be checked, for up to 72 hours; a GIS delivery is never sent unchecked.
- Summaries. Imports and bulk edits arrive only as one
gis.features.changedper asset type, with counts and up to 500 ids (more: truewhen there were more;itemsHintis an items address that answers what changed). Above 10,000 GIS deliveries a month for your company, the rest of the month's asset events arrive as one summary per asset type and hour. Summaries go only to webhooks subscribed togis.features.changed: subscribe to it if you subscribe to the single asset events. - Timing. Usually about 2 to 3 minutes after the change, and only while the GIS database is running: changes made while it is not running are sent when it next runs. It can take up to about 15 minutes in some conditions, and longer for bursts of thousands of changes. Retries follow the usual schedule (1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours).
- Payloads. A single asset event carries the asset as it is now, as the items route returns it, plus a
selflink. Above 256 KB the geometry is left out (geometry: null,geometryOmitted: true): read the item from itsselflink. Change-request statuses aresubmitted,in_review,accepted,rejected,done,cancelled. Deliveries can arrive out of order: comparerevisionorupdatedAt. - A new webhook never receives older changes. Events that happen while API access or the GIS API is off are never delivered later.
gis.feature.created
{
"id": "g_28d86c2b95627445aa1f",
"event": "gis.feature.created",
"createdAt": "2026-10-20T10:02:15.000Z",
"companyId": "your-company-id",
"data": {
"type": "Feature",
"id": "8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"geometry": {
"type": "Point",
"coordinates": [
-6.26031,
53.349805
]
},
"properties": {
"typeId": "5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"status": "In service",
"label": "P-1042",
"revision": 3,
"createdAt": "2026-10-01T08:12:00.000Z",
"updatedAt": "2026-10-14T15:40:21.000Z",
"attributes": {
"material": "Steel",
"height_m": 9.5,
"inspected": true
},
"photoCount": 2,
"latestPhotoAt": "2026-10-14T15:39:02.000Z",
"photosPageUrl": "https://fieldgrid.cloud/manager/gis?feature=8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b&photos=1&company=your-company-id"
},
"links": [
{
"rel": "self",
"type": "application/geo+json",
"href": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items/8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b"
}
]
}
}gis.feature.deleted
{
"id": "g_6a5f2a13a13d42d709c5",
"event": "gis.feature.deleted",
"createdAt": "2026-10-20T10:02:15.000Z",
"companyId": "your-company-id",
"data": {
"id": "7d6c5b4a-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
"reason": "deleted"
}
}gis.features.changed
{
"id": "g_464a9072055a01141c7c",
"event": "gis.features.changed",
"createdAt": "2026-10-20T10:02:15.000Z",
"companyId": "your-company-id",
"data": {
"group": "c41b7e0a9d2f4c6e",
"source": "import",
"typeId": "5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c",
"counts": {
"created": 2,
"updated": 1,
"deleted": 0,
"left": 0
},
"ids": [
"8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"7d6c5b4a-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
"6c5b4a3e-2f1d-4b0a-8f9e-8d7c6b5a4f3e"
],
"leftIds": [],
"more": false,
"itemsHint": "https://fieldgrid.cloud/api/v1/gis/ogc/collections/5b0c9e1a-2f4d-4c8e-9a7b-1d2e3f4a5b6c/items?datetime=2026-10-20T09%3A58%3A12.000Z%2F.."
}
}Every GIS event's example payload is on the webhooks page.
QGIS
QGIS 3.14 or later reads OGC API Features (fieldGRID tested QGIS 3.44).
- In fieldGRID, create a key with “Read GIS data” and copy it (Getting access).
- In QGIS: Layer → Add Layer → Add WFS / OGC API - Features Layer (or the Data Source Manager, WFS / OGC API - Features tab).
- Under Server Connections, choose New. Name:
fieldGRID. URL:https://fieldgrid.cloud/api/v1/gis/ogc. - Under Authentication, add a configuration (the + button): type Basic authentication, any user name, the key as the password. QGIS keeps it in its own protected authentication store. Do not put the key in the URL.
- Version: OGC API - Features (or Maximum / automatic). Optionally set the page size up to 1,000. Save.
- Choose Connect: your asset types are listed. For a type with several geometry kinds, pick its
(points),(lines)or(polygons)collections. - Optionally tick Only request features overlapping the view extent (QGIS then asks by
bboxas you pan), select the collections and choose Add.
The layers are read-only. The asset type's fields are inside attributes; photosPageUrl in the attribute table opens the asset's photos in fieldGRID. A map tip is plain HTML and cannot send the key, so a photo address in a map tip is refused; use photosPageUrl there.
GDAL and ogr2ogr
GDAL's OAPIF driver (GDAL 3.x; fieldGRID tested 3.10) reads every collection, follows the pages and turns a spatial filter into bbox. On Windows, use set NAME=value (Command Prompt) or $env:NAME="value" (PowerShell) instead of export.
# The key goes in an environment variable, never in the address.
export GDAL_HTTP_AUTH=BASIC
export GDAL_HTTP_USERPWD="fieldgrid:$FIELDGRID_GIS_KEY"
# GDAL does not retry a 429 on its own: let it wait and carry on.
export GDAL_HTTP_MAX_RETRY=10 GDAL_HTTP_RETRY_DELAY=1
# List the collections (asset types) the key can see
ogrinfo -ro "OAPIF:https://fieldgrid.cloud/api/v1/gis/ogc"
# Summary of one collection
ogrinfo -ro -so "OAPIF:https://fieldgrid.cloud/api/v1/gis/ogc" <collectionId>
# One collection to a GeoPackage (GDAL follows the pages)
ogr2ogr -f GPKG poles.gpkg "OAPIF:https://fieldgrid.cloud/api/v1/gis/ogc" <collectionId>
# Every collection into one GeoPackage, 1,000 assets per request
ogr2ogr -f GPKG -oo PAGE_SIZE=1000 everything.gpkg "OAPIF:https://fieldgrid.cloud/api/v1/gis/ogc"
# Only an area (sent to the API as bbox): minimum longitude, minimum latitude, maximum longitude, maximum latitude
ogr2ogr -f GeoJSON -spat -6.30 53.33 -6.22 53.36 area.geojson "OAPIF:https://fieldgrid.cloud/api/v1/gis/ogc" <collectionId>GDAL's -where cannot filter on the asset type's fields (they are inside attributes); filter in the request instead (status=, <field>=<value>, for example with curl) or after the download. Keep the key out of scripts you share: read it from an environment variable or your secret store.
ArcGIS
Works with OGC API Features clients such as QGIS and GDAL. Tested with the ArcGIS Maps SDK engine (.NET and JavaScript); ArcGIS Pro and the ArcGIS Online Map Viewer have not yet been validated by fieldGRID.
What fieldGRID checked with the ArcGIS Maps SDK engine (for .NET, and for JavaScript from an arcgis.com page): single-kind and per-kind collections load as feature layers and match fieldGRID; Basic authentication through the SDK's credentials (.NET) or the Authorization header added by a request interceptor (JavaScript) works; a missing or revoked key, and a key without “Read GIS data”, are refused; a page in credentials mode is blocked. Known limits of those Esri clients:
- First page only. A layer reads only the first page, at most 1,000 assets, and does not follow
nextlinks. Use a map extent (a smaller area) to read the part you need. - One geometry type per layer. Use the per-kind collections (
~point,~line,~polygon); a type's “(all geometries)” collection is refused or shows one kind only. - Layer filters are not supported. A layer filter (definition expression /
where) is sent as a parameter OGC API Features does not have, and the API refuses it (400 invalid_parameter) rather than return everything. - Popup images need custom content. A popup's image element cannot send the key, so a photo address there is refused. Fetch the photo through the SDK's own request (which adds the key) in custom popup content, or show
photosPageUrlas a link.
ArcGIS Maps SDK for JavaScript
import esriConfig from "@arcgis/core/config.js";
import OGCFeatureLayer from "@arcgis/core/layers/OGCFeatureLayer.js";
// The key goes in the Authorization header of every request to the API, never in a URL.
esriConfig.request.interceptors.push({
urls: "https://fieldgrid.cloud/api/v1/gis/ogc",
before(params) {
params.requestOptions.headers = { ...params.requestOptions.headers, Authorization: "Basic " + btoa("fieldgrid:" + key) };
},
});
const poles = new OGCFeatureLayer({ url: "https://fieldgrid.cloud/api/v1/gis/ogc", collectionId: "<collectionId>", outFields: ["*"] });The page must be served from an arcgis.com address or one of your company's ArcGIS Enterprise origins (Browsers).
ArcGIS Pro and the ArcGIS Online Map Viewer
Neither has been validated by fieldGRID yet. In fieldGRID's check of the ArcGIS Online Map Viewer without an ArcGIS account (Add layer from URL), the Map Viewer recognises the address as an OGC feature layer but does not ask for credentials: it offers only custom URL parameters, which the API refuses for a key, and reports “Service does not exist or is inaccessible”. Adding the API to the Map Viewer is therefore not possible today; a signed-in ArcGIS Online or ArcGIS Enterprise account with stored credentials has not been tried. If you try ArcGIS Pro, Esri documents the connection under Insert → Connections → Server → New OGC API Server: use server authentication with any user name and the key as the password, and no custom request parameters (the API refuses a key in the address). Tell us what you see at contact@fieldgrid.cloud.
Versioning
The GIS API is part of /api/v1 and follows the same promise: within v1 we only add. New routes, parameters, properties and events can appear; nothing is removed, renamed or changed in meaning. Your asset types' own fields inside attributes are yours: they change when you change the asset type. The full policy and every change are on versioning and changelog. Coordinate systems other than http://www.opengis.net/def/crs/OGC/1.3/CRS84, writing and filters beyond equality are not offered today; tell us what you need at contact@fieldgrid.cloud.
