Skip to content
EN

Build an Infrastructure Inspection Map with the Integration API

Last updated

Using Re:Earth CMS’s Integration API, you’ll build a map-based app for reporting the condition of public infrastructure — manholes, streetlights, traffic signs, and public benches. A small backend holds the Integration API token and makes every CMS request on the app’s behalf, so the token never reaches the browser.

An inspector opens a map, clicks an infrastructure asset, and submits a condition report with an issue category, a severity, notes, and optional photos. Submitting a report creates and publishes an item in CMS through the Integration API, then updates the asset’s own condition and last-inspected date. A “View report history” button lists every report submitted so far.

Architecture diagram: the Inspector uses the Browser, which calls the Backend's own routes such as /api/reports; the Backend calls the Integration API to reach Re:Earth CMS; the Browser never calls the Integration API directly. The Public Infrastructure Inspection Map app, showing a map with four colour-coded infrastructure asset markers — manhole, streetlight, traffic sign, and public bench — and a legend in the bottom left.

Compare your project with the finished source code.

  • Node.js 20 or later
  • Git
  • A Re:Earth CMS workspace
  • Basic familiarity with TypeScript

In the Re:Earth CMS management console, create a project, then add two models to it:

  1. On the project’s Models screen, click New Model, enter a name, and click OK
  2. On the model’s Schema screen, under Add Field, click a field type to add each field below

infrastructure-assets — the assets shown on the map.

Field keyField typeOptionsRequired
asset-idText—Yes
asset-nameText—Yes
asset-typeOptionmanhole / streetlight / traffic_sign / public_benchYes
locationGeometry EditorPointYes
current-conditionOptiongood / fair / poor / critical / Not inspectedNo
last-inspected-atDate—No
reference-photoAsset—No
The Schema screen for the infrastructure-assets model in Re:Earth CMS, showing its seven fields: Asset ID, Asset Name, Asset Type, Location, Current Condition, Last Inspected At, and Reference Photo.

current-condition defaults to Not inspected on a newly created asset — you don’t need to set it yourself.

inspection-reports — the reports inspectors submit.

Field keyField typeOptionsRequired
asset-idText—Yes
asset-typeOptionmanhole / streetlight / traffic_sign / public_benchYes
locationGeometry EditorPointYes
inspection-dateDate—Yes
conditionOptiongood / fair / poor / criticalYes
issue-categoryOptionno_issue / damage / obstruction / missing_component / malfunction / cleaning_required / otherYes
severityOptionlow / medium / high / urgent / not_applicableYes
notesTextArea—No
photosAsset, support multiple values—No
report-statusOptionpending / approved / resolvedYes
The Schema screen for the inspection-reports model in Re:Earth CMS, showing its ten fields: Asset ID, Asset Type, Location, Inspection Date, Condition, Issue Category, Severity, Notes, Photos, and Report Status.
  1. From your personal account, open My Integrations and create a new Integration — see Creating an Integration for the full walkthrough
  2. From the workspace that holds your project, open Integrations and connect the Integration you created
  3. Set its role to maintainer

Copy the Integration’s token. Treat it like a password: never commit it, and never put it in frontend code.

Before touching the app, make one request directly — this is the exact shape the backend code in Step 3 builds on. Replace <base-url>, <workspace>, <project>, <assets-model>, and <token> with your own values: <base-url> is https://api.cms.reearth.io/api, <workspace> and <project> are visible in the browser’s URL bar when you’re viewing that workspace or project in the CMS management console, <assets-model> is the Model key shown when you created infrastructure-assets above, and <token> is the Integration token you just copied.

Terminal window
curl -X POST \
'<base-url>/<workspace>/projects/<project>/models/<assets-model>/items' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"fields": [
{ "key": "asset-id", "type": "text", "value": "MH-001" },
{ "key": "asset-name", "type": "text", "value": "Demo Manhole 001" },
{ "key": "asset-type", "type": "select", "value": "manhole" },
{ "key": "location", "type": "geometryEditor", "value": "{\"type\":\"Point\",\"coordinates\":[138.26,36.06]}" }
]
}'

CMS returns the created item, including the id you’ll need next:

{
"id": "01...",
"fields": [
{ "key": "asset-id", "type": "text", "value": "MH-001" },
{ "key": "asset-name", "type": "text", "value": "Demo Manhole 001" },
{ "key": "asset-type", "type": "select", "value": "manhole" },
{ "key": "location", "type": "geometryEditor", "value": "{\"type\":\"Point\",\"coordinates\":[138.26,36.06]}" },
{ "key": "current-condition", "type": "select", "value": "Not inspected" }
],
"refs": ["latest"]
}

New items are created as drafts — refs lists only latest. Publish it the same way the app does in Step 3:

Terminal window
curl -X POST \
'<base-url>/<workspace>/projects/<project>/models/<assets-model>/items/<item-id>/publish' \
-H 'Authorization: Bearer <token>'

refs now includes public alongside latest — the item is retrievable through Public API too.

This covers one asset. Add a few more — a streetlight, a traffic sign, a public bench — directly through CMS Studio instead of repeating curl: choose the model under Content, click New Item, fill in the fields, then Save. The app has nothing to put on the map until a few assets like these exist.

Step 2 — Clone the repository and configure the backend

Section titled “Step 2 — Clone the repository and configure the backend”
Terminal window
git clone https://github.com/eukarya-biz/infrastructure-inspection-tutorial.git
cd infrastructure-inspection-tutorial/backend
npm install
cp .env.example .env

On Windows (Command Prompt), use copy .env.example .env instead of cp .env.example .env.

Fill in .env with the values from Step 1.

VariableValue
PORTBackend port (default 3000)
CMS_BASE_URLYour Re:Earth CMS API base URL
CMS_WORKSPACE_IDThe workspace ID from Step 1
CMS_PROJECT_IDThe project ID from Step 1
CMS_ASSETS_MODELinfrastructure-assets’s model key
CMS_REPORTS_MODELinspection-reports’s model key
CMS_INTEGRATION_TOKENThe Integration token from Step 1
Terminal window
npm run dev

The backend starts at http://localhost:3000.

Step 3 — See how the backend uses the Integration API

Section titled “Step 3 — See how the backend uses the Integration API”

This is the part the first two steps were setting up for — every CMS write in this app goes through a handful of route handlers, all calling the Integration API with the token from .env.

backend/src/cms.ts builds every CMS request the same way: the project’s base URL, plus an Authorization: Bearer <token> header.

export function cmsProjectUrl(config: CmsConfig, path: string): string {
return `${config.baseUrl}/${config.workspaceId}/projects/${config.projectId}${path}`;
}
export function cmsHeaders(
config: CmsConfig,
extra?: Record<string, string>,
): Record<string, string> {
return {
Authorization: `Bearer ${config.token}`,
Accept: "application/json",
...extra,
};
}

POST /api/reports (backend/src/routes/reports.ts) builds a fields array and posts it to the reports model’s items endpoint.

const fields = [
{ key: "asset-id", type: "text", value: assetId },
{ key: "asset-type", type: "select", value: assetType },
{ key: "location", type: "geometryEditor", value: JSON.stringify(location) },
{ key: "inspection-date", type: "date", value: `${inspectionDate}T00:00:00+09:00` },
{ key: "condition", type: "select", value: condition },
{ key: "issue-category", type: "select", value: issueCategory },
{ key: "severity", type: "select", value: severity },
{ key: "report-status", type: "select", value: "pending" },
];
if (validPhotoIds.length > 0) {
fields.push({ key: "photos", type: "asset", value: validPhotoIds });
}
await fetch(cmsProjectUrl(config, `/models/${modelKey}/items`), {
method: "POST",
headers: cmsHeaders(config, { "Content-Type": "application/json" }),
body: JSON.stringify({ fields }),
});

New items are created as drafts. The app publishes immediately after creating one, using the same Integration API token:

await fetch(`${itemUrl}/publish`, {
method: "POST",
headers: cmsHeaders(config),
});

Once a report is published, PATCH /api/assets/:itemId (backend/src/routes/assets.ts) updates the asset’s own current-condition and last-inspected-at, then republishes it so the change is visible through Public API too:

await fetch(itemUrl, {
method: "PATCH",
headers: cmsHeaders(config, { "Content-Type": "application/json" }),
body: JSON.stringify({
fields: [
{ key: "current-condition", type: "select", value: condition },
{ key: "last-inspected-at", type: "date", value: inspectedAt },
],
}),
});
await fetch(`${itemUrl}/publish`, {
method: "POST",
headers: cmsHeaders(config),
});

The full handlers add validation and error handling around these calls — see backend/src/routes/reports.ts and backend/src/routes/assets.ts for the complete code.

Terminal window
cd ../frontend
npm install
npm run dev

Open http://localhost:5173. The frontend never calls the Integration API directly — it only calls the backend’s own routes (/api/assets, /api/reports, and so on), and Vite’s dev server proxies those to the backend on port 3000. The token stays on the backend throughout.

Click an asset on the map to see its popup, then Start inspection to open the report form.

A popup on the map for Demo Traffic Sign 001, showing its asset ID and current condition, with a Start inspection button. The New inspection report form, filled in with an inspection date, condition set to Fair, issue set to Damage, severity set to Medium, and a notes field describing a bent, overgrown sign post.

Fill in the form and click Submit report. This calls the backend routes from Step 3 in order: upload any photos, create the report, publish it, then update the asset. On success, the map’s status bar shows “Report submitted and <assetId> updated”. The View report history button lists every report submitted so far, including any attached photos.

The Report history dialog, listing three inspection reports for a traffic sign, a streetlight, and a public bench, each with its condition, issue, severity, status, and notes — the traffic sign's report includes a photo.

Step 5 (optional) — Connect Re:Earth Visualizer

Section titled “Step 5 (optional) — Connect Re:Earth Visualizer”

This step is optional — it’s a bonus on top of the app itself, not something the rest of the tutorial depends on.

The two CMS models can also be read directly from the browser, with no backend involved, using Public API.

  1. In each model’s Public API settings, open the Reading tab, enable the model, and click Save Changes
  2. Note each model’s .geojson endpoint: https://api.cms.reearth.io/api/p/<workspace>/<project>/<model>.geojson

Then, in Re:Earth Visualizer:

  1. Create a new project
  2. For each model, click + New Layer → Add Layer from Resource, choose the GeoJSON tab, set Source Type to From Web (not the default From Assets), paste the model’s .geojson endpoint as the Resource URL, then click Add to Layer
  3. Open the Publish tab and click Publish

Anyone with the published URL can now see the same infrastructure assets and inspection reports on Visualizer’s map — reading the same CMS project your backend writes to.

The published Re:Earth Visualizer project, showing the four infrastructure asset markers on a map, read directly from the CMS Public API.

Creating a report returns invalid value

The value sent for an Option field doesn’t exactly match one of the options defined on the model — check issue-category, severity, and the other Option fields against the schema from Step 1.

The map loads with no assets

Confirm the backend’s .env points at the right workspace, project, and model keys, and that at least one item exists in infrastructure-assets.

Photos don’t appear in Visualizer

Visualizer reads Public API, which only serves published items. Confirm the report was published, not left as a draft.

  • The map shows the infrastructure assets created in Step 1
  • Submitting an inspection report creates and publishes an item in inspection-reports
  • The inspected asset’s condition and last-inspected date update after a report is submitted
  • Report history lists every submitted report
  • (optional) Re:Earth Visualizer shows the same data through Public API