Globhe

Developer Docs

Developer API · v1

Globhe Public API

Place and track drone-survey orders programmatically from your own systems. Authenticate with an API key, call the same order flow the Globhe platform uses, and pull deliverables when they are ready.

Base URL

https://api.globhe.com/api/v1/public

Overview

The Public API exposes Globhe's order flow to your integration. A single API key represents one client account: every order you create, and every order, mission, and deliverable you can read, is scoped to that account automatically. You never pass a client identifier — it is derived from the key.

All requests are made over HTTPS to the base URL above. Request and response bodies are JSON.

Authentication

Send your key as a bearer token on every request. An x-api-key header is also accepted.

Authorization header

Authorization: Bearer glb_live_ab12cd34_9f8e...

Keys carry scopes that limit what they can do. A call missing the required scope returns 403.

ScopeGrants
orders:readList and read orders, missions, planning.
orders:writeCreate orders and cancel / archive / reject them.
files:readList delivery packages and get signed download URLs.
webhooks:readList webhook endpoints and read their delivery logs.
webhooks:writeRegister and delete webhook endpoints.

Getting a key

API keys are issued on approval. From your Globhe dashboard, submit a request describing your intended use. A Globhe admin reviews it, and on approval the key is shown to you exactly once — copy it immediately and store it securely. If it is lost, revoke it and request a new one.

Keys can be revoked at any time from the dashboard and stop working immediately.

Responses & errors

Every response uses the same envelope:

Response envelope

{
  "success": true,
  "message": "Order created successfully",
  "data": { /* endpoint payload */ }
}

Errors use the same shape with the relevant HTTP status:

StatusMeaning
400Validation failed, or an unknown field was sent.
401Missing, malformed, expired, or revoked key.
403Key lacks the required scope, or the resource isn't yours.
404Resource not found.

Quickstart — an end-to-end flow

A typical integration is five steps. Register a webhook first so you never miss an update, create your order, then react to events as the mission progresses — no polling required. The examples use fetch in Node, but any HTTP client works.

Setup (shared by the calling steps)

const BASE = 'https://api.globhe.com/api/v1/public';
const KEY = process.env.GLOBHE_API_KEY;
const headers = {
  Authorization: `Bearer ${KEY}`,
  'Content-Type': 'application/json',
};
1

Register your webhook (once)

Point Globhe at your callback URL and store the returned secret — it is shown only once and is used to verify every event. Do this before creating orders so you catch the earliest events.

Node

const res = await fetch(`${BASE}/webhooks`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ url: 'https://example.com/hooks/globhe' }),
});
const { data } = await res.json();
// Persist data.secret securely — you cannot fetch it again.
saveSecret(data.secret); // e.g. "whsec_..."
2

(Optional) Turn a KML/CSV into mission locations

Have a survey file? Parse it to get ready-made locationMap arrays you can drop straight into the order. Skip this if you already have coordinates.

Node

const form = new FormData();
form.append('file', kmlBlob, 'survey-area.kml');
const res = await fetch(`${BASE}/kml/parse`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${KEY}` }, // let fetch set multipart Content-Type
  body: form,
});
const { data } = await res.json();
const placemarks = data.placemarks; // -> use as your missions' locationMap/labels
3

Create the order

Submit the order and its missions. You get back an orderCodeand the created missions — hang on to each mission's identifiers.

Node

const res = await fetch(`${BASE}/orders`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    order: {
      companyName: 'Acme Surveying',
      orderName: 'North Field Survey',
      orderType: 'singleLocation',
      sensors: ['rgbSensor'],
      deliverables: ['jpg', '_2dMap'],
      missionObjective: 'mapAndSurvey',
      industryAssetType: 'agriculture',
      startDate: '2026-08-01T00:00:00.000Z',
      deadline: '2026-08-15T00:00:00.000Z',
    },
    missions: [
      {
        missionName: 'Field A',
        missionType: 'area',
        country: 'Sweden',
        locationLabel: 'Uppsala North Field',
        areaSize: 42.5,
        locationMap: [
          { lat: 59.858, lng: 17.645 },
          { lat: 59.860, lng: 17.650 },
        ],
      },
    ],
  }),
});
const { data: order } = await res.json();
console.log(order.orderCode); // "ORD-000123"
4

Receive & verify events

As the mission moves forward you receive callbacks — typically mission.operator_assigned mission.flight_scheduledmission.data_delivered mission.completed. Verify the signature against the raw body, respond 2xx quickly, and de-duplicate on id.

Express handler

import express from 'express';
import { createHmac, timingSafeEqual } from 'crypto';

const app = express();

function isValid(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

app.post('/hooks/globhe', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!isValid(raw, req.header('X-Globhe-Signature'), loadSecret())) {
    return res.status(401).end();
  }
  res.status(200).end(); // ack fast

  const evt = JSON.parse(raw);
  if (alreadyProcessed(evt.id)) return; // idempotent on delivery id
  switch (evt.event) {
    case 'mission.data_delivered':
    case 'mission.completed':
      void downloadDeliverables(evt.data.mission.uuid); // step 5
      break;
  }
});
5

Download the data

On mission.data_delivered (or mission.completed), list the mission's delivery packages, then each package's files — every file carries a signed downloadUrl valid for 24 hours.

Node

async function downloadDeliverables(missionUid) {
  const pkgRes = await fetch(`${BASE}/missions/${missionUid}/delivery-packages`, { headers });
  const { data: packages } = await pkgRes.json();

  for (const pkg of packages.data) {
    const fileRes = await fetch(`${BASE}/delivery-packages/${pkg.uuid}/files`, { headers });
    const { data: files } = await fileRes.json();
    for (const file of files.data) {
      await download(file.downloadUrl, file.fileName); // 24h signed URL
    }
  }
}

That's the whole loop. This flow uses the webhooks:write, orders:write, orders:read, and files:read scopes — make sure your key carries them.

Import geometry (KML / CSV)

Have a KML or CSV of your survey area? Upload it and get back ready-to-use mission locations, so you don't have to hand-build coordinate arrays. Each placemark / row returns a locationMap (lat/lng pairs) plus a suggested locationLabel, country, and city — drop each one straight into the missions array when creating an order. The file is parsed in memory and never stored.

POST/kml/parsescope: orders:write

Send the KML as multipart form field file(max 10 MB).

Request

curl -X POST https://api.globhe.com/api/v1/public/kml/parse \
  -H "Authorization: Bearer glb_live_xxx" \
  -F "file=@survey-area.kml"

Response

{
  "success": true,
  "message": "KML parsed successfully",
  "data": {
    "placemarks": [
      {
        "locationLabel": "North Field",
        "country": "Sweden",
        "city": "Uppsala",
        "locationMap": [
          { "lat": 59.858, "lng": 17.645 },
          { "lat": 59.860, "lng": 17.650 }
        ]
      }
    ]
  }
}
POST/csv/parsescope: orders:write

Same as above, for a CSV of sites — send it as multipart field file. The response shape is identical (a placemarks array). Use the template below to see the expected columns.

Request

curl -X POST https://api.globhe.com/api/v1/public/csv/parse \
  -H "Authorization: Bearer glb_live_xxx" \
  -F "file=@sites.csv"
GET/csv/examplescope: orders:write

Returns a sample CSV template showing the columns the parser expects, so you can format your file correctly.

Orders

POST/ordersscope: orders:write

Create an order with one or more missions. The order is attached to your account automatically. Required order fields: companyName, orderName, orderType (singleLocation or multiLocation), sensors, deliverables, missionObjective, industryAssetType, startDate, deadline. Each mission needs a locationMap of lat/lng points, country, missionType, locationLabel, areaSize, and missionName.

Request

curl -X POST https://api.globhe.com/api/v1/public/orders \
  -H "Authorization: Bearer glb_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "order": {
      "companyName": "Acme Surveying",
      "orderName": "North Field Survey",
      "orderType": "singleLocation",
      "sensors": ["rgbSensor"],
      "deliverables": ["jpg", "_2dMap"],
      "missionObjective": "mapAndSurvey",
      "industryAssetType": "agriculture",
      "startDate": "2026-08-01T00:00:00.000Z",
      "deadline": "2026-08-15T00:00:00.000Z"
    },
    "missions": [
      {
        "missionName": "Field A",
        "missionType": "area",
        "country": "Sweden",
        "locationLabel": "Uppsala North Field",
        "areaSize": 42.5,
        "locationMap": [
          { "lat": 59.858, "lng": 17.645 },
          { "lat": 59.860, "lng": 17.650 }
        ]
      }
    ]
  }'

Response

{
  "success": true,
  "message": "Order created successfully",
  "data": {
    "uuid": "b6f1...",
    "orderCode": "ORD-000123",
    "orderName": "North Field Survey",
    "status": "biddingWithQuote",
    "orderType": "singleLocation",
    "startDate": "2026-08-01T00:00:00.000Z",
    "deadline": "2026-08-15T00:00:00.000Z",
    "createdAt": "2026-07-06T10:00:00.000Z"
  }
}

Accepted values

These fields are validated against fixed sets — sending any other value returns 400. sensors and deliverables accept multiple values, and jpg is always included in deliverables.

FieldAccepted values
orderTypesingleLocation, multiLocation
missionType (per mission)area, linear, asset
sensorsrgbSensor, thermalSensor, multispectralSensor, hyperspectralSensor, lidarSensor, groundCapture
deliverablesjpg, _2dMap, _3dMap, _3dModel, digitalTerrainModel, digitalSurfaceModel, topographicMap, thermalMap, ndvi, multispectral, hyperspectral, streamedData, raw, panoramas, LiDAR, pointCloud
missionObjectivemapAndSurvey, assetInspection, siteProgress, visualsMarketing
industryAssetTypetelecom, solarPanels, windTurbines, powerLines, construction, forest, agriculture, mining, railways, roadsBridges, oilGas, ports, realEstateProperty, waterManagement, other
GET/ordersscope: orders:read

List your orders, most recent first. Cursor-paginated via limit, cursor, and direction query parameters.

Request

curl "https://api.globhe.com/api/v1/public/orders?limit=20" \
  -H "Authorization: Bearer glb_live_xxx"
GET/orders/:codescope: orders:read

Fetch a single order by its code (e.g. ORD-000123), including its missions.

GET/orders/:code/missionsscope: orders:read

List the missions of an order. Use each mission's uuid for the mission-resource endpoints below.

PATCH/orders/:uuid/cancelscope: orders:write

Cancel an order you placed.

PATCH/orders/:uuid/archivescope: orders:write

Archive an order. Send a reason in the body.

Body

{ "reason": "Duplicate request" }
PATCH/orders/:uuid/rejectscope: orders:write

Reject an order. Send a reason in the body.

Mission resources

These are keyed by a mission's uuid, which you get from the order detail or missions list.

GET/missions/:missionUid/planningscope: orders:read

Full planning history for a mission (scheduling status, version, and who scheduled it).

GET/missions/:missionUid/flight-permissionscope: orders:read

The flight permission for a mission. When a permit document exists, a time-limited permitFileUrl is included — the raw storage path is never exposed.

GET/missions/:missionUid/delivery-packagesscope: files:read

The delivered data packages for a mission.

Data & files

GET/delivery-packages/:deliveryPackageUid/filesscope: files:read

List the files in a delivery package. Each file comes with a downloadUrl — a signed link valid for 24 hours. Fetch fresh URLs when they expire; do not cache them long-term.

Response

{
  "success": true,
  "message": "Files fetched successfully",
  "data": {
    "data": [
      {
        "uuid": "f12a...",
        "fileName": "orthomosaic.tif",
        "fileType": "image/tiff",
        "fileDataType": "orthomosaic",
        "fileSize": 184320000,
        "status": "delivered",
        "downloadUrl": "https://storage.googleapis.com/...signed...(24h)"
      }
    ],
    "nextCursor": null,
    "hasNextPage": false
  }
}

Webhooks

Instead of polling, subscribe a URL to receive an HMAC-signed callback the moment a mission hits a key milestone. Events are scoped to your account — you only receive callbacks for your own missions.

Events

EventFires when
mission.operator_assignedAn operator is assigned to the mission.
mission.flight_scheduledA flying date is confirmed.
mission.data_deliveredCaptured data passes Globhe review and is released to you.
mission.completedYou accept the delivered data — the mission is done.
POST/webhooksscope: webhooks:write

Register an endpoint. url must be https. Pass events to subscribe to specific events, or omit it to receive all of them. The response includes the signing secret exactly once — store it securely; it is never shown again.

Request

curl -X POST https://api.globhe.com/api/v1/public/webhooks \
  -H "Authorization: Bearer glb_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/globhe",
    "events": ["mission.operator_assigned", "mission.completed"],
    "description": "Prod integration"
  }'

Response

{
  "success": true,
  "message": "Webhook endpoint created successfully",
  "data": {
    "uuid": "a1b2...",
    "url": "https://example.com/hooks/globhe",
    "events": ["mission.operator_assigned", "mission.completed"],
    "description": "Prod integration",
    "status": "active",
    "lastStatus": null,
    "secret": "whsec_...",
    "createdAt": "2026-07-07T09:00:00.000Z",
    "updatedAt": "2026-07-07T09:00:00.000Z"
  }
}
GET/webhooksscope: webhooks:read

List your endpoints. Cursor-paginated via limit, cursor, and direction. The secret is never returned here.

GET/webhooks/:uuidscope: webhooks:read

Fetch a single endpoint by its uuid.

GET/webhooks/:uuid/deliveriesscope: webhooks:read

The delivery log for an endpoint — one row per event sent, with the outcome so you can spot failures.

Response

{
  "success": true,
  "message": "Webhook deliveries fetched successfully",
  "data": {
    "data": [
      {
        "uuid": "d4e5...",
        "event": "mission.completed",
        "success": true,
        "responseCode": 200,
        "attempts": 1,
        "error": null,
        "createdAt": "2026-07-07T09:05:00.000Z"
      }
    ],
    "nextCursor": null,
    "hasNextPage": false
  }
}
DELETE/webhooks/:uuidscope: webhooks:write

Delete an endpoint. Deliveries to it stop immediately.

Event payload

Every callback is a POST with this JSON body:

POST body

{
  "id": "evt_delivery_id",
  "event": "mission.operator_assigned",
  "timestamp": "2026-07-07T09:05:00.000Z",
  "data": {
    "mission": {
      "uuid": "…",
      "missionCode": "MSN-000123",
      "status": "pilotAssigned",
      "orderUid": "…",
      "orderCode": "ORD-000045"
    }
  }
}

Request headers

HeaderValue
X-Globhe-Signaturesha256=<hex> — HMAC-SHA256 of the raw body.
X-Globhe-EventThe event name.
X-Globhe-DeliveryThe delivery id (matches id in the body).
X-Globhe-TimestampISO-8601 send time.

Verifying the signature

Recompute the HMAC over the raw request body with your endpoint secret and compare it, in constant time, against the X-Globhe-Signature header. Reject the request if it does not match.

Node.js

import { createHmac, timingSafeEqual } from 'crypto';

function isValidGlobheSignature(rawBody, header, secret) {
  const expected =
    'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

Delivery & retries

Respond with any 2xx within ~5 seconds. On failure Globhe retries up to 3 times with a short backoff; delivery is best-effort (there is no durable queue), so treat the API as the source of truth and use the deliveries log to spot gaps. Retries reuse the same delivery id — make your handler idempotent on it. Ordering is not guaranteed; rely on status in the payload.