Developers · API v1

Build on Ubermach.

Send missions to real drones, follow them live and get every photo, video and analysis back into your own system. This guide explains every endpoint, field and error so your team can integrate without having to ask us.

Your first request
curl https://ubermach.com/api/v1/me \
  -H "Authorization: Bearer ubk_…"

{
  "org": { "id": "org_8f2c", "name": "Acme Inspections", "is_partner": true },
  "auth": "apikey",
  "clients": 3,
  "templates": ["fotos_lugar", "punto_fijo", "relevamiento_area", …],
  "events": ["mission.created", "mission.approved", …]
}
200 OK
Before you start

The API is part of the API plan.

Monthly fee + one charge per drone connected in the month. API keys are free.

01

Subscribe to the API plan

Create your company account and pay by card. Access opens as soon as the subscription is active.

/developers/alta
02

Create an API key

In the developer portal, create a key with only the permissions you need. It is shown once: store it on your server.

/developers/app → Keys
03

Make your first call

Call GET /me with the key. If it answers 200, your account, permissions and clients are ready.

GET /api/v1/me
Get started

Overview

The Ubermach API lets an external system — your field-service software, ERP or your own app — send work to drones and get the results back. You never talk to the drone directly: you create a mission, a person approves it, the pilot confirms the take-off on the remote controller, and Ubermach returns every photo, video, telemetry point and AI analysis, with webhooks at each step.

How the pieces fit

ConceptWhat it is
OrganizationYour company. Owns the API keys, the webhook and the bill. A partner organization can also have clients (child organizations) and act on their behalf.
ClientOne of your customers, identified by your own id (external_ref). Drones, zones and missions belong to a client.
DroneA registered aircraft (dev_…) with a capability profile. It connects through the phone connector with its own device key.
ZoneA geofence drawn in Ubermach Flights. Missions can only fly inside an operation zone and never inside an exclusion zone.
MissionA route for one drone, built from a template or your own waypoints, optionally with automatic AI on every photo.
SessionOne flight as recorded by the phone: telemetry, events, photos and videos, with a summary when it closes.
MediaA photo or video (med_…) with its position, hash and AI analyses.

Safety rules the API always enforces

  • Every route is checked against the client's zones and the 120 m ceiling before a mission is saved, again when it is approved and again when it is dispatched.
  • No API key can approve a mission or create a zone: those actions need a person signed in to the panel. Direct commands to a drone are off by default and only work if your administrator enables them in the developer portal (Direct control).
  • No take-off happens without the pilot's confirmation on the remote controller.
Get started

Access and pricing

The API (API keys, webhooks and the embeddable viewer) is a paid service, separate from Ubermach Flights. Your organization needs the API plan with an active subscription. None of the Flights plans include it.

  • Sign up at /developers/alta. The account is created with the API plan; access opens when the card subscription is authorized.
  • Monthly fee plus one charge per drone connected in the month. A drone counts once per month when it talks to Ubermach at least once (telemetry, connector or session), counted by distinct serial number. Deleting and re-creating a drone does not count it twice; deleting it before month end does not remove it from the bill.
  • If you connect your own clients' drones, all of them are billed to your organization, even with a single key.
  • API keys are free: create as many as you need, one per system or environment.
  • Prices, the 3-month pilot at half the platform fee and examples: ubermach.com/pricing. Payment is a monthly card subscription.

Without an active API plan every call made with an API key answers 402, viewer tokens stop working and webhooks are not sent:

402 Payment Required
{ "error": "La API de Ubermach es un servicio pago para integradores…", "code": "plan_limit", "limit": "api" }
Your subscription, keys, connected drones of the month and invoices are all in the developer portal.
Get started

Authentication

Every request carries your API key in the Authorization header. Keys start with ubk_ and belong to your organization, not to a person. Create, see and revoke them in the developer portal → Keys; the full key is shown only once.

Header
Authorization: Bearer ubk_…
  • The scheme is case-insensitive (Bearer or bearer), followed by one or more spaces and the key.
  • Client-restricted keys. When you create a key in the portal you can limit it to one client: it only sees and operates that client, and cannot change organization-wide settings (403 client_restricted_key). Use one per client if your system serves many customers, so a bug that mixes customers cannot cross data between them.
  • A revoked or unknown key answers 401. A valid key without the API plan answers 402. A key without the needed permission answers 403.
  • Use the API only from your server. The API sends no CORS headers, so browsers on other sites cannot call it, and a key inside a mobile app or a web page can be extracted. To show live video in your app, use the embeddable viewer.

Other credentials

PrefixWho uses itFor
ubk_Your serverEverything in this guide under Resources.
ubd_The phone or connector next to the droneSending telemetry, photos and videos, and receiving missions. One per drone. See Phone connector.
vt_The embeddable viewer in your appWatching one drone live for a limited time. Your server requests it. See Embeddable viewer.
whsec_Your webhook receiverVerifying that a webhook comes from Ubermach. See Webhooks.
Get started

Permissions

Each key has a list of permissions chosen when you create it. Give each system only what it needs: a reporting tool only needs read permissions.

PermissionAllows
—GET /me and GET /templates: any valid key.
read:devicesList drones and their live state, command history, viewer layouts.
read:telemetryTelemetry by time range, the live channel, viewer tokens, session tracks and full session telemetry.
read:flightsFlights, sessions and session events.
read:missionsMissions, zones and the approval queue.
read:mediaPhotos, videos, their files and AI analyses, the AI summary.
control:missionsCreate, dispatch and cancel missions.
control:droneSend direct commands to a drone, only if direct control is enabled in the developer portal. See Direct control.
propose:zonesPropose new zones (they stay pending until a person approves them in Ubermach Flights). See Propose a zone.
ai:analyzeRun AI on a photo, set automatic AI.
admin:clientsCreate clients, register drones, edit capabilities, rotate device keys, manage viewer layouts and designer tokens.
admin:webhooksConfigure, test and inspect the webhook.
Never possible with an API key, whatever its permissions: approving missions, creating or approving zones, managing API keys, direct control settings and billing. These answer 403 and are done by a person in the panel or the developer portal.
Get started

Requests and responses

ItemRule
Base URLhttps://ubermach.com/api/v1
Alias/api/v1/x3a/… answers the same for the resource routes. Existing integrations use it; new ones should use /api/v1/…. URLs returned by the API (files, tracks, live) still use the alias: follow them as they come.
FormatJSON in and out, UTF-8. File downloads, GeoJSON, NDJSON and CSV are the exceptions.
Body sizeJSON bodies up to 512 KiB (413 above). An empty body, or an empty array [], counts as {}; any other body that is not a JSON object answers 400.
Server timeEvery response carries X-Server-Time (ms). Compare it with your clock to detect drift: telemetry with a timestamp more than 5 minutes ahead is rejected.
IdsStrings with a prefix: dev_, msn_, med_, ses_, org_… Treat them as opaque.
Your idsexternal_ref, external_id, external_form, external_field: up to 120 characters in every resource. Longer values answer 400 field_too_long; they are never cut.
TimesMilliseconds since the Unix epoch (UTC), as integers.
CoordinatesWGS84 decimal degrees. Points are { "lat", "lng" }; polygons are lists of [lng, lat], like GeoJSON.
ClientsWherever a route accepts client, you can send the Ubermach id or your own external_ref.
ListsNewest first. limit sets the page size (each route says its default and maximum; a value that is not a positive integer answers 400). When there are more results the response has next: send it as after to get the next page. since (ms) returns only items from that time on, for incremental syncs. Paged: clients, drones, missions, photos and videos, sessions.
Other organizationsA resource that belongs to an organization you cannot see answers 404, never 403, so ids cannot be probed.

Using your own ids

Send your own identifiers and Ubermach keeps them and returns them in every response and webhook: external_ref for a client, external_id for the task that originated a mission, a session or a file. external_id is for correlation and can repeat (the same task can be flown again). If your system replaces a provisional id with the final one, move everything with re-link an external id.

Safe retries: Idempotency-Key

Send an Idempotency-Key header (up to 120 letters, numbers, ., _, - or :) on POST /missions, unique per operation, for example task-48213:attempt-2. Repeating the request with the same key within 24 hours returns the same response, with Idempotent-Replayed: true, without creating anything new. Creating clients and registering drones are idempotent by themselves (external_ref and serial number).

cURL
curl -X POST https://ubermach.com/api/v1/missions   -H "Authorization: Bearer ubk_…" -H "Content-Type: application/json"   -H "Idempotency-Key: task-48213:attempt-2" -d @mision.json
Get started

Errors

Errors return an HTTP status and a JSON body. error is a message in Spanish, ready to show to a person. Every error has a stable code: branch on the code, never on the text, which can change. When there is no more specific code, it comes from the status (invalid_request, unauthorized, forbidden, not_found, conflict, unprocessable, rate_limited, internal_error…).

JSON
{
  "error": "La ruta no cumple las zonas o la altitud.",
  "code": "zone_violation",
  "problems": [
    { "index": 3, "problem": "altitud 140 m supera el máximo 120 m" },
    { "segment": 4, "problem": "el tramo entre waypoints cruza una zona no permitida" }
  ]
}
StatusMeaning
400Invalid data. The message names the field.
401Missing, unknown or revoked key (or an expired viewer token: code: "token_expired").
402A plan limit: code: "plan_limit" with limit = api, devices, flights, storage, ai or live.
403The key lacks the permission, or the action needs a person in the panel.
404It does not exist, or it belongs to an organization you cannot see.
405Wrong method for that route.
409The resource is not in a state that allows the action (for example, dispatching a mission that is not approved). The message says why.
413 · 415Body too large, or a content type that is not accepted.
422A valid request the zones or the drone cannot accept: problems (zones and altitude) or code: "capability_missing".
429Too many requests on a rate-limited route. Wait the seconds in Retry-After.
5xxA problem on our side or in a service we depend on (AI, detection). Retry later with backoff.

Stable error codes

CodeStatusExtra fields and what to do
plan_limit402limit. Renew or upgrade the plan; retrying will not help.
capability_missing422required and what the drone does have (device_control or device_commands). The drone cannot do it: change the mission or the drone. Do not retry.
token_expired401Viewer token expired: request a new one from your server.
zone_violation422problems lists each waypoint or leg outside the zones or above the allowed altitude.
mission_pending_approval · mission_not_approved409The mission is waiting for a person, or is not in a state that can be dispatched. Wait for mission.approved.
device_offline · battery_low · device_busy409The drone has not talked to Ubermach in 30 s, its battery is below 30 %, or it already has an active mission.
field_too_long400field and max tell which value is too long. Shorten it; it is never cut.
invalid_country · invalid_time_zone400Country must be an exact ISO 3166 two-letter code; time zone an IANA name.
invalid_limit · invalid_cursor · invalid_since · invalid_idempotency_key400A list or header parameter has the wrong format.
invalid_device_key · no_valid_points401 · 400Device key unknown or revoked; or a telemetry batch where every point was rejected (see rejected).
client_restricted_key · too_many_keys403 · 409A client-restricted key tried an organization-wide action; or a drone already has 50 active device keys.
api_control_disabled403Direct control is off for your organization. An administrator turns it on in the developer portal → Control.
api_control_command403That command is not among the allowed ones.
api_control_client403Direct control is not allowed for that client's drones.
Get started

Limits and retries

API keys have no request quota. The limits that do exist protect the drone and the live channel:

WhereLimit
POST /bridge/detect3 requests per second per drone (429 with Retry-After: 1).
Live channelUp to 50 viewers per drone, 10 of them with video. Telemetry is relayed at most 5 times per second.
ListsEach route caps limit (stated in each route).
PlanThe plan of each client may cap drones, flights, storage, AI photos and live view (402 plan_limit).

When to retry

  • Retry network errors, 429 and 5xx, waiting longer each time (1 s, 2 s, 4 s…).
  • Do not retry 400, 401, 402, 403, 404 or 422: the same request will fail the same way.
  • A 409 depends on state: read the resource again, then decide.
  • Creating a mission is safe to retry only if you send external_id.
Get started

Versioning

The version is in the path: /api/v1. Within v1 we only make changes that do not break a correct integration:

  • New routes, new optional fields in requests, new fields in responses and new webhook events can appear at any time. Ignore fields you do not know.
  • Removing or renaming a field, changing its type or meaning, or making something required would be a new version, announced in advance.
  • The machine-readable specification is always at GET /api/v1/openapi.json (public, no key).
cURL
curl https://ubermach.com/api/v1/health

{ "ok": true, "service": "ubermach-system", "time": 1792540800000 }
Get started

Integration checklist

What your platform needs to have in place before going live. Each point avoids a failure we have seen in field integrations.

Keys and data isolation

  • Keep ubk_ and whsec_ in your secret store or environment, never in a database table or in code.
  • If you serve many customers, consider one client-restricted key per customer. Always send client explicitly.
  • Never pass Ubermach ids (media, sessions, missions) from a browser to your server without checking that the signed-in user belongs to that client_ref.

Ids and retries

  • Use your final task id as external_id. If you only have a provisional one (an offline task), send it and re-link it as soon as you have the final id.
  • Send Idempotency-Key on every mission creation, and make your jobs safe to run twice: a scheduled job that runs on two servers must not create two missions.
  • Branch on code, never on the error text.

Your webhook receiver

  • Public https with no redirects: a trailing slash, a language prefix or http→https redirect makes every delivery fail.
  • Verify the signature over the raw body, store the event, answer 2xx in under a second and process it afterwards.
  • Ignore repeated X-X3A-Delivery ids and do not rely on arrival order.
  • After an outage, catch up with the event log instead of waiting for retries.

Time and text

  • Times are milliseconds UTC. Convert with integer division (intdiv($ms, 1000) in PHP) before storing in a seconds column, and show them in the client's time zone (time_zone of the client).
  • Text is full UTF-8: AI descriptions can contain any character. If your database cannot store 4-byte characters, turn on ascii_only in the webhook and use utf8mb4 for new tables.
  • On the phone, compare your clock with X-Server-Time and check rejected in each telemetry answer before deleting points locally.

Drones, zones and approvals

  • Register each drone by serial number (repeating it is safe) and give each phone its own device key. Do not rotate the original key on every error: it disconnects the other phones.
  • Manual flights (the pilot flying with the controller) need no mission, zone or approval: open a session and upload what the drone captures.
  • Autonomous missions need an operation zone for the client and a person who approves. During onboarding, propose the zones and decide who approves; send them the approval_url you get when a mission is created.
  • Check usage and limits before a field visit so a plan limit does not stop a flight on site.
Resources

Account

Two routes any valid key can call. Use /me as a health check of your credentials when your system starts.

Resources · Account

Who am I

GET/api/v1/me

Returns the organization that owns the key, how many clients it has, the mission templates and the webhook events you can subscribe to.

Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl $API/me -H "$KEY"
Response · 200
JSON
{
  "org": {
    "id": "org_8f2c", "name": "Acme Inspections", "country": "US",
    "is_partner": true, "external_ref": null,
    "parent_org_id": null, "parent_name": null
  },
  "auth": "apikey",
  "clients": 3,
  "templates": ["fotos_lugar", "punto_fijo", "relevamiento_area", "fachada", "video_recorrido"],
  "events": ["mission.created", "mission.approved", …],
  "plan": null, "billing": null, "avatar_v": null
}
What can go wrong
StatusWhenWhat to do
401Missing, unknown or revoked key.Check the header and the key in the portal.
402 plan_limitThe organization has no active API plan.Renew the subscription in the portal.
Resources · Account

Mission templates

GET/api/v1/templates

Every template with its pattern, description and parameters (type, range and default). The same information is in Templates.

Response · 200
JSON
{
  "templates": {
    "fotos_lugar": { "name": "…", "pattern": "orbit", "description": "…", "params": { … } },
    …
  }
}
Resources

Clients

If your organization is a partner, each of your customers is a client: a child organization with its own drones, zones, missions and files. You identify each one with your own id (external_ref, for example your account number). Organizations that are not partners work with their own drones and skip this section.

Resources · Clients

Create or update a client

POST/api/v1/clientsadmin:clients

Idempotent by external_ref: the first call creates the client (201); later calls with the same external_ref update its name and country (200). Call it every time a customer is created or renamed in your system.

Body
FieldTypeDescription
external_refstring · requiredYour id for this customer. Up to 80 characters.
namestring · requiredName shown in Ubermach. Up to 120 characters.
countrystringExact two-letter ISO 3166 code (US, MX, ES…). Send it always; anything else answers 400 invalid_country.
time_zonestringOptional IANA time zone (America/Mexico_City), to show local times. Kept if you omit it later.
Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl -X POST $API/clients -H "$KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_ref":"858","name":"Delta Construction","country":"US"}'
Response · 201
JSON
{ "id": "org_k21x", "external_ref": "858", "name": "Delta Construction",
  "country": "US", "time_zone": "America/New_York", "created": true }
What can go wrong
StatusWhenWhat to do
400external_ref or name missing, or a country that is not two letters.Fix the body.
403Your organization is not a partner.Ask Ubermach to enable partner mode, or register the drones in your own organization.
Resources · Clients

List clients

GET/api/v1/clientsadmin:clients

Your clients, newest first, up to 1000 per page (limit, after, since). Not available to client-restricted keys.

Response · 200
JSON
{ "clients": [
  { "id": "org_k21x", "name": "Delta Construction", "country": "US", "time_zone": "America/New_York",
    "external_ref": "858", "created_at": 1792540800000 }
], "next": null }
Resources

Drones

A drone is registered once, with its serial number. Registration returns its device key (ubd_…), which goes into the phone or connector that flies it. From then on, the drone reports its position and battery and appears online while it has talked to Ubermach in the last 30 seconds.

Resources · Drones

Register a drone

POST/api/v1/devicesadmin:clients

Creates the drone and returns its original device key only once. Idempotent by serial number: registering the same serial again for the same client answers 200 with the existing drone, existing: true and no key, so retrying after a timeout is safe. To give a key to another phone, create a device key.

Body
FieldTypeDescription
serialstring · requiredSerial number: 4 to 40 letters, numbers or dashes (stored in capitals).
clientstringClient id or external_ref. Without it, the drone is yours.
modelstringAircraft model, up to 60 characters.
controllerstringRemote controller, up to 60 characters.
nicknamestringFriendly name, up to 60 characters.
adapterstringHow it connects: mavlink, dji_v5, dji_v4, dji_cloud, tello, toy_wifi or import. Loads a default capability profile.
capabilitiesobjectFull capability profile, if you want to declare it yourself. See Capabilities.
Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl -X POST $API/devices -H "$KEY" \
  -H "Content-Type: application/json" \
  -d '{"client":"858","serial":"1581F5FHD23","model":"DJI Mavic 3E",
       "nickname":"North site","adapter":"dji_v5"}'
Response · 201
JSON
{
  "id": "dev_4k2p", "client_id": "org_k21x", "serial": "1581F5FHD23",
  "adapter": "dji_v5", "capabilities": { "control": "missions", … },
  "ingest_key": "ubd_…"
}
What can go wrong
StatusWhenWhat to do
400Invalid serial, unknown adapter or an invalid capability profile.Fix the body; the message names the field.
402 plan_limitThe client's plan does not allow more drones (limit: "devices").Upgrade the plan or remove a drone.
Resources · Drones

Device keys, one per phone

POST/api/v1/devices/{id}/keysadmin:clients

A drone flown from several phones (or a phone replaced) should not share one key. Create one key per phone or connector: each is shown once, works exactly like the original key, and can be revoked alone without disconnecting the others. Up to 50 active keys per drone. The original key keeps working.

GET /devices/{id}/keys lists them (prefix, label, dates). DELETE /devices/{id}/keys/{keyId} revokes one.

Body
FieldTypeDescription
labelstringOptional, up to 120 characters: for example the phone id in your system.
Response · 201
JSON
{ "id": "dky_5qL…", "device_id": "dev_4k2p", "label": "phone-42",
  "key_prefix": "ubd_umN1P6", "created_at": 1792540800000, "key": "ubd_…" }
What can go wrong
StatusWhenWhat to do
409 too_many_keysThe drone already has 50 active keys.Revoke the ones you no longer use.
Resources · Drones

List drones with live state

GET/api/v1/devicesread:devices

Drones newest first, up to 500 per page (limit, after, since), each with its last telemetry point and its active mission. GET /devices/{id} returns one drone with the same shape.

Query
FieldTypeDescription
clientstringOnly the drones of this client (id or external_ref).
Response · 200
JSON
{ "devices": [{
  "id": "dev_4k2p", "client_id": "org_k21x", "serial": "1581F5FHD23",
  "model": "DJI Mavic 3E", "controller": "DJI RC Pro", "nickname": "North site",
  "adapter": "dji_v5", "capabilities": { … }, "capabilities_source": "bridge",
  "online": true, "last_seen_at": 1792540812000,
  "telemetry": { "ts": 1792540811500, "lat": 40.4168, "lng": -3.7038, "alt_m": 42.5,
    "speed_ms": 6.1, "heading_deg": 228, "battery_pct": 81, "satellites": 19,
    "mode": "P-GPS", "link_quality": 96, "flight_id": "flt_…", "data": { … } },
  "active_mission": { "id": "msn_9x31", "name": "…", "status": "running", "external_id": "48213" }
}] }
Resources · Drones

Telemetry history

GET/api/v1/devices/{id}/telemetryread:telemetry

Points in a time range, newest first. Each point has normalized fields (lat, lng, alt_m, speed_ms, heading_deg, battery_pct, satellites, mode, link_quality) and data, the full object the drone sent (attitude, gimbal, signal, detailed battery, home point…). For a whole flight, the session routes are easier.

Query
FieldTypeDescription
fromintegerStart, in ms. Default: one hour before to.
tointegerEnd, in ms. Default: now.
limitinteger1 to 5000. Default 1000.
include_details1Also return the connector's per-message records (in data.bridge.record). They do not add points to the track.
Response · 200
JSON
{ "device_id": "dev_4k2p", "from": 1792537200000, "to": 1792540800000,
  "points": [ { "ts": 1792540799000, "lat": 40.4168, … , "data": { … } }, … ] }
Resources · Drones

Flights of a drone

GET/api/v1/devices/{id}/flightsread:flights

Recorded flights of one drone: start, end, points, distance and maximum altitude. limit 1 to 200, default 50.

Response · 200
JSON
{ "flights": [ { "id": "flt_…", "device_id": "dev_4k2p", "started_at": …, "ended_at": …,
  "status": "closed", "points": 1840, "distance_m": 2310, "max_alt_m": 61, "session_id": "ses_…" } ] }
Resources · Drones

Command history

GET/api/v1/devices/{id}/commandsread:devices

The last 100 commands sent to the drone (mission starts, pauses, returns home…) and what happened to each: pending → sent → done or rejected, or expired if the drone did not pick it up in 5 minutes (returns home and landings never expire).

Response · 200
JSON
{ "commands": [ { "id": "cmd_…", "type": "mission_start", "status": "done",
  "mission_id": "msn_9x31", "params": { … }, "detail": null,
  "created_at": …, "sent_at": …, "acked_at": … } ] }
Resources · Direct control

Send a direct command

POST/api/v1/devices/{id}/commandscontrol:drone

Lets your platform act on a drone in flight: bring it home, land it, pause or resume its mission, take a photo, record or tilt the camera. It is off by default. It only works when all of these hold:

  • An administrator of your organization turned on direct control in the developer portal → Control.
  • The key has the control:drone permission.
  • That command is among the allowed ones, and the drone belongs to an allowed client (or all clients are allowed).
  • The drone's capability profile supports it.

Your platform decides which of its users see these controls. The pilot stays on site with the remote controller and can take over at any time. Every command is recorded in the audit log with the key that sent it.

Body
FieldTypeDescription
typestring · requiredreturn_home, land, pause, resume, take_photo, start_recording, stop_recording or set_gimbal.
pitchnumberOnly with set_gimbal: −90 (straight down) to 30.
Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl -X POST $API/devices/dev_4k2p/commands -H "$KEY" \
  -H "Content-Type: application/json" -d '{"type":"return_home"}'
Response · 202
JSON
{ "id": "cmd_…", "type": "return_home", "status": "pending", "device_online": true }
What can go wrong
StatusWhenWhat to do
403 api_control_*Direct control is off (api_control_disabled), the command is not allowed (api_control_command) or the client is not allowed (api_control_client). Also without control:drone.Ask your administrator to change it in the portal → Control.
409pause or resume without an active mission, or resume before it started.Read the drone's active_mission first.
422 capability_missingThe drone cannot do what is asked (no mission control, no camera…).Do not retry. Change the mission or the drone.

The command waits in a queue (202) until the phone next to the drone picks it up. Follow it with GET /devices/{id}/commands or the command.acked webhook. Commands not picked up in 5 minutes expire, except return_home and land, which never expire and are resent until the drone answers.

Resources · Drones

Rotate a device key

POST/api/v1/devices/{id}/rotate-keyadmin:clients

Generates a new ubd_ key. The old one stops working at once and the drone's live connection is closed, so the phone must be updated with the new key. Use it when a key is lost or a phone is replaced.

Response · 200
JSON
{ "id": "dev_4k2p", "ingest_key": "ubd_…" }
Resources

Capabilities

Each drone has a capability profile: how it can be controlled, which commands it accepts, its camera and its sensors. The API rejects up front what the drone cannot do (422 capability_missing) instead of queuing it. A drone without a profile is not checked.

FieldValues
controlmissions (flies missions), manual (direct commands only) or none (import only). Missions need missions.
commandspause, resume, return_home, land, take_photo, start_recording, stop_recording, set_gimbal
camera{ photo, video, live, gimbal }, booleans. Photo actions need photo; video actions need video. A gimbal angle on a drone without gimbal is only a warning.
sensorsgps, rtk, imu, attitude, compass, barometer, battery, rangefinder, optical_flow, obstacle, thermal, lidar

Where the profile comes from (capabilities_source): declarado (you or the adapter default), bridge (what the connector detected on the real drone, which replaces what was declared) or sesion.

Resources · Capabilities

Declare or fix a profile

PUT/api/v1/devices/{id}/capabilitiesadmin:clients

Send capabilities, or just an adapter to load its default profile. It is only written (and audited) if something changed. If the connector later reports what it detects, that wins.

Request
JSON
{ "adapter": "mavlink",
  "capabilities": { "control": "missions", "commands": ["pause","resume","return_home","land","take_photo"],
    "camera": { "photo": true, "video": true, "live": false, "gimbal": true },
    "sensors": ["gps","imu","barometer","battery"] } }
Response · 200
JSON
{ "device": { "id": "dev_4k2p", "adapter": "mavlink", "capabilities": { … },
  "capabilities_source": "declarado", … } }
What can go wrong
StatusWhenWhat to do
400Unknown adapter, invalid profile, or neither capabilities nor an adapter with a default profile.Fix the body.
Resources

Missions

A mission is what you ask the drone to do. You create it from a template or with your own waypoints; Ubermach validates it against the client's zones; an administrator approves it in Ubermach Flights (it can be the same person who created it); you dispatch it; the pilot confirms the take-off on the remote controller; and webhooks tell you when it starts and finishes, with every photo.

  • draft
  • approved
  • dispatched
  • running
  • done · aborted
StatusMeaningWebhook
draftCreated and validated, waiting for an administrator's approval.mission.created
approvedApproved in the panel. You can dispatch it.mission.approved
dispatchedSent to the drone. Waiting for the pilot to start it. If the drone rejects it, it goes back to approved.mission.dispatched
runningThe drone is flying it.mission.started
doneFinished. The webhook carries every file.mission.completed
abortedCancelled by you, or the drone landed before finishing.mission.cancelled · mission.completed
Resources · Missions

Create a mission

POST/api/v1/missionscontrol:missions

Creates a mission for one drone, from a template or from your own waypoints. The route is checked against the client's operation zones, exclusion zones and the 120 m ceiling before it is saved, and the drone's capabilities are checked too. A new mission is always draft: an administrator approves it in Ubermach Flights, then you dispatch it. Take-off always needs the pilot's confirmation on the remote controller.

Body
FieldTypeDescription
device_idstring · requiredThe drone that will fly. Must belong to you or one of your clients.
templatestringfotos_lugar, punto_fijo, relevamiento_area, fachada or video_recorrido, with its params.
paramsobjectTemplate parameters. See Templates.
waypointsarrayInstead of a template: 1 to 200 points { lat, lng, alt_m, heading_deg?, gimbal_pitch?, action? }. heading_deg 0–360, gimbal_pitch −90 to 30, action = photo, start_recording or stop_recording.
namestringUp to 120 characters. Default: the template name.
external_idstringYour task id (up to 120). Without Idempotency-Key, sending it again while that mission is still open returns it (200, existing: true); after it finished or was aborted, a new mission is created, so the task can be flown again. With Idempotency-Key, the key decides.
aiobjectAnalyze every photo automatically: { "mode": "detect" | "describe" | "ask", "prompt" }. prompt (up to 500) is required with ask.
Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl -X POST $API/missions -H "$KEY" \
  -H "Content-Type: application/json" -d '{
  "device_id": "dev_4k2p",
  "external_id": "48213",
  "name": "Roof inspection",
  "template": "fotos_lugar",
  "params": { "center": { "lat": 40.4168, "lng": -3.7038 },
              "radius_m": 40, "alt_m": 35, "photos": 12 },
  "ai": { "mode": "describe" }
}'
Response · 201
JSON
{
  "mission": {
    "id": "msn_9x31", "client_id": "org_k21x", "device_id": "dev_4k2p",
    "name": "Roof inspection", "status": "draft", "external_id": "48213",
    "source": "api", "template": "fotos_lugar", "template_params": { … },
    "ai": { "mode": "describe", "prompt": null },
    "waypoints": [ … 12 ], "created_at": 1792540800000,
    "approved_at": null, "dispatched_at": null, "closed_at": null
  },
  "approval_url": "https://flights.ubermach.com/app#/aprobaciones",
  "dispatch_error": null,
  "warnings": []
}
What can go wrong
StatusWhenWhat to do
400Invalid template parameters or waypoints, or an invalid ai block.Fix the body; the message names the problem.
404The drone does not exist or is not yours.Check the id and the organization of the key.
422 problemsThe route leaves the operation zones, enters an exclusion zone, crosses one between waypoints, or goes above the allowed altitude. Also when the client has no operation zone.Fix the route with the list in problems, or ask the client to draw a zone in Flights.
422 capability_missingThe drone cannot do what is asked (no mission control, no camera…).Do not retry. Change the mission or the drone.

Send Idempotency-Key to make retries safe (see Requests). approval_url opens the approval queue in Ubermach Flights: send it to whoever approves (an administrator of your organization or of the client). warnings lists non-blocking notices (for example, a gimbal angle on a drone without gimbal). The old dispatch: true option no longer dispatches: the response explains it in dispatch_error.

Resources · Missions

List missions

GET/api/v1/missionsread:missions

Newest first, paged with after and next; since for incremental syncs. Filter to find the missions of one of your tasks with external_id.

Query
FieldTypeDescription
clientstringClient id or external_ref.
statusstringOne of the statuses above.
device_idstringOnly this drone.
external_idstringExact match with your task id.
limitintegerUp to 200. Default 50.
Response · 200
JSON
{ "missions": [ { "id": "msn_9x31", "status": "approved", … } ] }
Resources · Missions

Mission detail

GET/api/v1/missions/{id}read:missions

The mission with its photos and videos (each with download URLs) and its commands.

Response · 200
JSON
{ "mission": { … },
  "media": [ { "id": "med_…", "kind": "photo", "url": "…", "preview_url": "…", … } ],
  "commands": [ { "id": "cmd_…", "type": "mission_start", "status": "done", … } ] }
Resources · Missions

Approve a mission

POST/api/v1/missions/{id}/approvePanel only

A person with the administrator role approves the mission in Ubermach Flights → Approvals; it can be the same person who created it. The route is validated against the zones again. No API key can approve: it answers 403. Listen for the mission.approved webhook, or read the approval queue.

Resources · Missions

Dispatch a mission

POST/api/v1/missions/{id}/dispatchcontrol:missions

Sends an approved mission to the drone (no body). Checks, in this order, that the mission is approved, that the drone can fly missions, that it is online (it talked to Ubermach in the last 30 seconds), that its last reported battery is at least 30 %, that it has no other active mission, and that the route still complies with the zones. The pilot then confirms the take-off on the remote controller.

Response · 202
JSON
{ "mission": { "id": "msn_9x31", "status": "dispatched", … } }
What can go wrong
StatusWhenWhat to do
409 mission_pending_approvalNobody approved it yet.Wait for mission.approved.
409 device_offlineThe drone has not talked to Ubermach in the last 30 seconds.Ask the pilot to power it and connect the app; retry.
409 battery_low · device_busyBattery below 30 %, or the drone already has an active mission.Change battery or wait for the other mission.
422 capability_missingThe drone cannot do what is asked (no mission control, no camera…).Do not retry. Change the mission or the drone.
422 problemsThe route leaves the operation zones, enters an exclusion zone, crosses one between waypoints, or goes above the allowed altitude. Also when the client has no operation zone.Fix the route with the list in problems, or ask the client to draw a zone in Flights.

Idempotent: dispatching a mission that is already dispatched or running answers 200 with already: true.

Resources · Missions

Re-link an external id

POST/api/v1/external-ids/remapcontrol:missions

When your system replaces a provisional id with the final one (for example a task created offline on the phone), move every mission, session and file from from to to in one operation. Only within what the key can see; use client to limit it to one client.

Body
FieldTypeDescription
fromstring · requiredThe id that was used until now.
tostring · requiredThe final id (up to 120).
clientstringClient id or external_ref (optional).
Request
JSON
{ "from": "tmp:1729435200123", "to": "4812345", "client": "858" }
Response · 200
JSON
{ "from": "tmp:1729435200123", "to": "4812345",
  "updated": { "missions": 1, "sessions": 2, "media": 14 } }
Resources · Missions

Cancel a mission

POST/api/v1/missions/{id}/cancelcontrol:missions

Moves the mission to aborted from any open status. If it was dispatched, its pending commands expire. If the drone is already flying it, a return-home command is queued automatically and its id comes back in the response.

Response · 200
JSON
{ "id": "msn_9x31", "status": "aborted", "return_home_command": "cmd_…" }
What can go wrong
StatusWhenWhat to do
409The mission is already closed (done or aborted).Nothing to do.
Resources

Templates

A template builds the waypoints for you from a few parameters. Values outside the range answer 400; missing optional values take the default. Altitudes are in meters above the take-off point, and the zones always have the last word.

TemplateWhat it doesParameters · range (default)
fotos_lugarOrbit of photos around a point.center {lat, lng} required · radius_m 10–150 (40) · alt_m 5–120 (35) · photos 4–36 (12) · gimbal_pitch −90–0 (−30)
punto_fijoFour photos (N, E, S, W) from one point.center required · alt_m 5–120 (40) · gimbal_pitch −90–0 (−45)
relevamiento_areaGrid survey over a polygon, with overlap, for maps and models.area 3–500 points [lng, lat] · alt_m 20–120 (60) · front_overlap 0.5–0.9 (0.75) · side_overlap 0.4–0.9 (0.65) · camera_hfov_deg 40–120 (82) · gimbal_pitch −90–0 (−90). At most 200 photos.
fachadaColumns of photos facing a façade between two points A → B.area [A, B] at least 2 m apart · side left | right (left) · standoff_m 5–60 (15) · alt_min_m 2–120 (5) · alt_max_m 2–120 (30) · vertical_step_m 2–30 (5) · horizontal_step_m 2–50 (8). At most 200 photos.
video_recorridoA path with continuous video from the first to the last point.area 2–200 points [lng, lat] · alt_m 5–120 (40) · gimbal_pitch −90–0 (−20)
Resources

Zones and approvals

Zones are geofences drawn by the client's administrators in Ubermach Flights → Zones. There are three kinds: operacion (missions may only fly inside one), restringida (with its own maximum altitude) and exclusion (no flying). Without at least one operation zone, no mission can be created for that client. Your key can read zones but never create, change or approve them.

Resources · Zones

List zones

GET/api/v1/zonesread:missions

Current zones (active) and proposals waiting for approval (pending). A pending proposal does not count yet when routes are validated. Use client to choose the client.

Response · 200
JSON
{ "zones": [ {
  "id": "zon_…", "client_id": "org_k21x", "name": "North site",
  "kind": "operacion", "ring": [[-3.705, 40.417], [-3.702, 40.417], [-3.702, 40.415], …],
  "max_alt_m": 90, "status": "active", "replaces_id": null, … } ],
  "max_alt_m": 120 }
Resources · Zones

Propose a zone

POST/api/v1/zonespropose:zones

Proposes a new zone for a client. It is born pending and does not count for missions until a person with the administrator role approves it in Ubermach Flights → Approvals. No API key can approve, change or remove zones. Useful during onboarding: propose the operation zone of each site from the coordinates you already have.

Body
FieldTypeDescription
client_idstringClient id or external_ref.
namestring · requiredUp to 80 characters.
kindstring · requiredoperacion · restringida · exclusion
ringarray3 to 500 vertices [lng, lat].
center · radius_mobject · numberInstead of ring: a center { lat, lng } and a radius of 10 to 5000 m.
max_alt_mnumber1 to 120 (not used for exclusion zones).
Request
JSON
{ "client_id": "858", "name": "North site", "kind": "operacion",
  "center": { "lat": 40.4168, "lng": -3.7038 }, "radius_m": 300, "max_alt_m": 90 }
Response · 201
JSON
{ "zone": { "id": "zon_…", "status": "pending", "kind": "operacion", "ring": [ … 32 ], … } }
Resources · Zones

Approval queue

GET/api/v1/approvalsread:missions

What is waiting for a person: draft missions of the last 30 days and zone changes, plus what was decided in the last 24 hours. Missions you created appear as requested by “Integración (API)”.

Response · 200
JSON
{ "pending": [ … ], "history": [ … ], "me": null }
Resources

Photos and videos

Every file the drone captures arrives with its position, time, SHA-256 hash and, if requested, its AI analyses. Files are linked to their mission, session and to your external_id, so you can attach them to your task.

Resources · Media

List photos and videos

GET/api/v1/mediaread:media
Query
FieldTypeDescription
mission_idstringFiles of one mission.
device_idstringFiles of one drone.
external_idstringFiles of one of your tasks.
clientstringFiles of one client.
limitintegerUp to 500. Default 100.
Response · 200
JSON
{ "media": [ {
  "id": "med_7hq", "kind": "photo", "content_type": "image/jpeg",
  "bytes": 5242880, "sha256": "9f2c…", "sha256_verified": true,
  "lat": 40.4168, "lng": -3.7038, "alt_m": 35, "heading_deg": 90, "gimbal_pitch": -30,
  "taken_at": 1792540900000, "mission_id": "msn_9x31", "session_id": "ses_…",
  "external_id": "48213", "external_filename": "IMG_0042.JPG", "duration_s": null,
  "url": "https://ubermach.com/api/v1/x3a/media/med_7hq/file",
  "preview_url": "…?variant=preview", "watermarked_url": "…?variant=watermarked"
} ] }
Resources · Media

A file and its analyses

GET/api/v1/media/{id}read:media
Response · 200
JSON
{ "media": { … },
  "analyses": [ { "id": "ana_…", "mode": "describe", "prompt": null,
    "model": "@cf/meta/llama-3.2-11b-vision-instruct",
    "result": { "text": "A flat roof with two air-conditioning units…" }, "created_at": … } ] }
Resources · Media

Download a file

GET/api/v1/media/{id}/fileread:media

Returns the binary file. The organization that owns the photo receives the original. Any other organization that can see it (for example, a partner looking at a client's photo) receives a watermarked copy: “UBERMACH” tiled, plus a strip with the id, UTC time, position and the start of the original's SHA-256. Videos are always delivered as originals.

cURL
curl -L "$API/media/med_7hq/file" -H "$KEY" -o IMG_0042.jpg
Query
FieldTypeDescription
variantstringpreview (small JPEG), original (owner only) or watermarked (anyone, also the owner, to share it).
What can go wrong
StatusWhenWhat to do
403variant=original from an organization that does not own the photo.Use the watermarked copy.
404No preview for that file, or the file is not available.Ask for the original.
Resources

AI analysis

Ubermach can analyze photos (not videos) in three modes. Ask for it on demand, or automatically for every photo of a mission (ai when creating it). Results are stored with the photo and arrive by the analysis.completed webhook.

ModeResult
detectObjects with label, score (≥ 0.5) and box, up to 50, plus counts of people and vehicles. A general model, not specialized in aerial imagery.
describe{ "text" }: a description of the scene in Spanish.
ask{ "question", "answer" }: the answer to your question about the photo.
Resources · AI

Analyze a photo

POST/api/v1/media/{id}/analyzeai:analyze

Each call creates a new analysis and counts towards the client's AI photos of the month.

Body
FieldTypeDescription
modestring · requireddetect, describe, ask
promptstringYour question, required with ask. Up to 500 characters.
Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl -X POST $API/media/med_7hq/analyze -H "$KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"ask","prompt":"¿Hay daños visibles en el techo?"}'
Response · 200
JSON
{ "analysis": { "id": "ana_…", "mode": "ask",
  "model": "@cf/meta/llama-3.2-11b-vision-instruct",
  "result": { "question": "¿Hay daños visibles en el techo?", "answer": "…" } } }
What can go wrong
StatusWhenWhat to do
400A video, an unknown mode, or ask without a prompt.Fix the request.
402 plan_limitThe client reached its AI photos for the month (limit: "ai").Upgrade the plan or wait for next month.
409A photo over 5 MB without a preview.The phone must upload its preview first.
502 · 503The AI did not answer, or it is not enabled.Retry later.
Resources · AI

Analyses and monthly summary

GET/api/v1/ai/analysesread:media

The latest analyses with their photo, drone and mission. Filter with client, mode (detect, describe, ask or skipped for photos not analyzed because of the plan) and limit (up to 200, default 50).

GET /api/v1/ai/summary returns the current month: analyses by mode, detections by class and skipped photos. PUT /api/v1/ai/settings with { "ai_auto": "detect" | "describe" | null } (permission ai:analyze) turns automatic AI on or off for every uploaded photo.

Resources

Flight sessions

The phone opens a session for every flight and closes it when it lands. Everything captured — telemetry, events, photos, videos — is linked to it, and on close Ubermach computes a summary and sends session.closed. Sessions carry your external_id, so you can find the flights of one task.

Resources · Sessions

List sessions

GET/api/v1/sessionsread:flights
Query
FieldTypeDescription
external_idstringSessions of one of your tasks.
clientstringClient id or external_ref.
device_idstringOnly this drone.
statusstringopen · closed
limitinteger1 to 200. Default 50.
Response · 200
JSON
{ "sessions": [ {
  "id": "ses_2mf", "client_ref": "858", "device_id": "dev_4k2p", "flight_id": "flt_…",
  "external_id": "48213", "pilot_ref": "…", "adapter": "dji_v5", "drone_model": "DJI Mavic 3E",
  "status": "closed", "started_at": …, "ended_at": …,
  "summary": { "duration_s": 912, "distance_m": 2310, "max_alt_m": 61, "points": 1840,
    "battery_start_pct": 98, "battery_end_pct": 41, "photos": 12, "videos": 1 },
  "track_url": "https://ubermach.com/api/v1/x3a/sessions/ses_2mf/track.geojson"
} ] }
Resources · Sessions

Session detail

GET/api/v1/sessions/{id}read:flights

The session, its flight (points, distance, maximum altitude) and its files (up to 500). GET /sessions/{id}/events returns its events (log, alert, mode, command, link), limit up to 2000, default 500.

Response · 200
JSON
{ "session": { … }, "flight": { "points": 1840, "distance_m": 2310, "max_alt_m": 61, … },
  "media": [ … ] }
Resources · Sessions

Track and full telemetry

GET/api/v1/sessions/{id}/track.geojsonread:telemetry

The flight path as a GeoJSON FeatureCollection with one LineString, at most one point per second; properties.coord_times has the time of each coordinate. Ready for any map library.

GET /sessions/{id}/telemetry streams every point of the session: format=ndjson (default, one JSON per line) or format=csv (columns ts, lat, lng, alt_m, speed_ms, heading_deg, battery_pct, satellites, mode, link_quality, data). Add include_details=1 for the connector's detailed records.

cURL
curl "$API/sessions/ses_2mf/telemetry?format=csv" -H "$KEY" -o vuelo.csv
Resources

Flights

A flight is the track Ubermach builds from telemetry, with or without a session. Without a session, a gap of more than 2 minutes closes the flight and opens a new one.

Resources · Flights

Flights of your organization

GET/api/v1/flightsread:flights

Your organization's flights, newest first. Filter with device_id; limit up to 200, default 50. GET /api/v1/flights/{id} returns one.

This route only sees the key's own organization. To see a client's flights, use GET /devices/{id}/flights or the sessions. It has no /x3a alias.
Resources

Live channel

A WebSocket that pushes what the drone is doing as it happens. Open it from your server with your key; to show live video to your users without exposing your key, use the embeddable viewer.

Resources · Live

Watch a drone live

WSwss://ubermach.com/api/v1/devices/{id}/liveread:telemetry

On connect you receive a snapshot with the last point and whether the drone is online; then telemetry (at most 5 per second), event and presence. You may send {"type":"ping"} (answered with pong); anything else you send is ignored.

Request
Node.js
import WebSocket from "ws";

const ws = new WebSocket("wss://ubermach.com/api/v1/devices/dev_4k2p/live", {
  headers: { Authorization: `Bearer ${process.env.UBM_KEY}` },
});
ws.on("message", (raw, isBinary) => {
  if (isBinary) return;             // video frames, only with ?video=1
  const msg = JSON.parse(raw);
  if (msg.type === "telemetry") console.log(msg.point.lat, msg.point.lng, msg.point.battery_pct);
});
Messages
JSON
{ "type": "snapshot", "online": true, "session_id": "ses_2mf", "last": { … } }
{ "type": "telemetry", "session_id": "ses_2mf", "point": { "ts": …, "lat": …, … } }
{ "type": "event", "session_id": "ses_2mf", "event": { "type": "alert", … } }
{ "type": "presence", "online": false, "session_id": null }
What can go wrong
StatusWhenWhat to do
402 plan_limitThe client's plan does not include live view (limit: "live").Upgrade the client's plan.
426The request was not a WebSocket upgrade.Use a WebSocket client.
42950 viewers (or 10 with video) already connected.Share one connection in your server.

Video

With ?video=1 you also receive the drone's video as binary messages: byte 0 is the type (1 H.264 key frame in Annex B with SPS/PPS, 2 H.264 delta, 3 JPEG), bytes 1–8 are the capture time in ms (float64, big-endian), and the rest is the frame. You get the last key frame right after connecting. In a browser it decodes with WebCodecs (VideoDecoder).

Resources

Embeddable viewer

To show a drone's live video inside your own app or website, without decoding anything and without exposing your key: your server asks for a short-lived token, and your page shows an iframe with the embed_url it returns. The viewer brings the HUD (altitude, speed, battery, map…), the watermark and the reconnection logic.

Resources · Viewer

Get a viewer token

POST/api/v1/devices/{id}/live-tokenread:telemetry

Only with an API key, from your server. The token is only valid for this drone. Ask for a new one each time a user opens the view.

Body (optional)
FieldTypeDescription
ttl_sintegerLifetime in seconds, 60 to 3600. Default 600.
layoutstringHUD layout name (lowercase letters, numbers and dashes, up to 40).
rolestringviewer (default) or designer, which can also edit and save layouts and needs admin:clients.
Request
HTML
<!-- your server inserts embed_url -->
<iframe src="https://ubermach.com/embed/live/dev_4k2p#token=vt_…"
        width="960" height="540" allow="fullscreen"></iframe>
Response · 201
JSON
{ "token": "vt_…", "role": "viewer", "expires_in": 600,
  "expires_at": 1792541400000,
  "embed_url": "https://ubermach.com/embed/live/dev_4k2p#token=vt_…" }
What can go wrong
StatusWhenWhat to do
401 token_expiredThe token expired while the viewer was open.The viewer emits token_expired: ask your server for a new token and reload the iframe.
403Requested with a panel session instead of a key, or designer without admin:clients.Request it from your server with the right key.
Resources · Viewer

HUD layouts

PUT/api/v1/viewer/layouts/{name}admin:clients

Create or replace a HUD layout, for all your clients or for one (?client=). GET /viewer/layouts (read:devices) lists yours and the presets completo, minimo, busqueda and solo-video; DELETE /viewer/layouts/{name} removes one.

Up to 24 elements. type: estado, bateria, alertas, altura, velocidad, gimbal, horizonte, mapa, rumbo, mision, hora, coordenadas. anchor: top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right. x/y offsets up to ±1080, size S, M or L, visible.

Request
JSON
{ "elements": [
  { "type": "estado", "anchor": "top-left", "size": "M" },
  { "type": "bateria", "anchor": "top-right" },
  { "type": "mapa", "anchor": "bottom-right", "size": "L" }
] }
Resources

Webhooks

Ubermach calls your server when something happens, so you do not have to poll. One webhook per organization (your clients' events go to yours). Configure it with PUT /webhook; the response brings the signing secret only once.

Resources · Webhooks

Configure the webhook

PUT/api/v1/webhookadmin:webhooks
Body
FieldTypeDescription
urlstring · requiredPublic https address. Local and private addresses are rejected.
eventsarrayEvents to receive. Empty or missing: all of them.
rotate_secretbooleanGenerate a new secret (the old one stops signing).
ascii_onlybooleanSend non-ASCII characters escaped as \uXXXX. Same JSON for any parser; for receivers whose database cannot store 4-byte characters.
Request
cURL
API=https://ubermach.com/api/v1
KEY="Authorization: Bearer ubk_…"

curl -X PUT $API/webhook -H "$KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/ubermach/webhook",
       "events":["mission.approved","mission.completed","analysis.completed"]}'
Response · 200
JSON
{ "url": "https://example.com/ubermach/webhook",
  "events": ["mission.approved", "mission.completed", "analysis.completed"],
  "secret": "whsec_…" }
What can go wrong
StatusWhenWhat to do
400Not https, a private address, or an unknown event.Fix the body.
403The key belongs to a client organization: webhooks are configured on the partner.Use the partner's key.

GET /webhook shows the configuration (without the secret), DELETE /webhook turns it off, POST /webhook/test sends a ping, and GET /webhook/deliveries lists the last 50 deliveries with their status, attempts and last error.

What arrives

Each delivery is a POST with a JSON body { event, created_at, org_id, data }. org_id is the organization where it happened (one of your clients, or you).

Headers
Content-Type: application/json
User-Agent: X3A-Webhooks/1
X-X3A-Event: mission.completed
X-X3A-Delivery: 1234
X-X3A-Signature: t=1792540900,v1=5b1f…
EventWhendata
mission.createdA mission was created.mission_id, external_id, device_id, waypoints, approval_url
mission.approvedA person approved it: you can dispatch.mission_id, external_id, device_id
mission.dispatchedSent to the drone.mission_id, external_id, device_id, command_id
mission.startedThe drone started flying it.mission_id, external_id, device_id
mission.completedFinished or aborted in flight, with every file.mission_id, external_id, device_id, result, detail, media[]
mission.cancelledYou cancelled it.mission_id, external_id, device_id, return_home_command
command.ackedThe drone answered a command.command_id, type, status, detail, mission_id, external_id, device_id
media.createdA photo or video arrived.The file (as in GET /media) + client_ref
analysis.completedAn AI analysis finished.analysis_id, media_id, mission_id, mode, model, result, external_id, device_id, session_id, client_ref…
session.startedThe phone opened a flight session.session_id, external_id, device_id, client_ref, adapter, drone_model, started_at
session.closedThe session closed, with its summary and files.The above + ended_at, summary, track_url, media[] (up to 200; media_truncated if more)
session.updatedLate telemetry arrived for a closed session.Same as session.closed
pingYou called POST /webhook/test. Always sent, whatever your event filter.message

Verify the signature

v1 is the hex HMAC-SHA256 of t + "." + raw body, keyed with your whsec_ secret. Compute it over the raw body, before parsing the JSON, compare in constant time, and reject timestamps older than 5 minutes.

Node.js
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Deliveries and retries

  • Answer with any 2xx within 8 seconds. Redirects are not followed and count as a failure.
  • Up to 7 attempts over about 21 hours: right away, then about 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours after each failure (retries run every 5 minutes, so short waits round up). After the seventh failure the delivery is marked failed; you can redeliver it.
  • Delivery is at least once: the same event can arrive twice. Use X-X3A-Delivery (stable across retries) to ignore duplicates. Order is not guaranteed: rely on created_at and on the resource status.
  • Process slow work after answering: save the event, answer 200, then handle it.
Resources · Webhooks

Event log and redelivery

GET/api/v1/webhook/events?after=admin:webhooks

Every event addressed to your webhook in the last 30 days, oldest first, with the same body you receive. After an outage, read from the last delivery_id you processed (after) until next_after is null, instead of waiting for retries. limit up to 500, default 100.

POST /webhook/deliveries/{id}/redeliver sends one delivery again, with the same X-X3A-Delivery, so your receiver recognizes it if it had already processed it.

Response · 200
JSON
{ "events": [ { "delivery_id": 1234, "event": "mission.completed", "status": "failed",
  "created_at": 1792540900000, "org_id": "org_k21x", "data": { … } } ],
  "next_after": 1234 }
Resources

Usage and limits

Each client may have its own Flights plan with limits on drones, flights, storage, AI photos and live view. When a limit is reached the API answers 402 plan_limit, even in the middle of a field visit. Check before the visit.

Resources · Usage

Usage and limits of a client

GET/api/v1/usage?client=read:devices

The current month for one client (or for your organization without client). plan: null and limits: null mean no Flights limits apply. One client per call.

Response · 200
JSON
{ "client_id": "org_k21x", "plan": { "id": "profesional", "name": "Profesional" },
  "limits": { "devices": 10, "flights": 1000, "photos": 15000, "storage_gb": 300, "live": true, "history_days": null },
  "usage": { "devices": 4, "flights": 212, "photos": 3210, "storage_gb": 41.2 } }
Phone connector

Device keys

This part is for whoever builds the app or connector that runs next to the drone (an Android app with the DJI SDK, a MAVLink connector…). If you only integrate from your server, you can skip it: Ubermach's own app already does all of this.

  • Each drone has its original ubd_ key, returned once when it is registered. Give each phone or connector its own extra key with device keys, so you can revoke one without disconnecting the others. A key can only act on its own drone.
  • Send it as Authorization: Bearer ubd_…. An unknown or revoked key answers 401 invalid_device_key.
  • Use the X-Server-Time header of any answer to correct the phone clock: telemetry more than 5 minutes ahead of the server is rejected.
  • Every call marks the drone as connected this month (that is what is billed per drone) and as online for 30 seconds.
  • The phone usually talks to the drone over one network and to the internet over another. Calls to Ubermach must go out over a network with internet (normally mobile data); with Wi-Fi drones, bind only the drone's sockets to its Wi-Fi network.
These routes live under /api/v1/x3a/… (also /api/v1/…), except telemetry ingestion and commands, which only exist as /api/v1/ingest/telemetry and /api/v1/bridge/commands.
Phone connector

Sessions and telemetry

Phone connector · Sessions

Open a session

POST/api/v1/x3a/bridge/sessionsubd_

Open one session per flight, before take-off. Idempotent by client_session_id: a retry returns the same session with 200 instead of 201.

Body
FieldTypeDescription
client_session_idstring · requiredYour id for this flight: 8 to 64 letters, numbers, _ or -.
external_id · external_form · pilot_refstringTask, form and pilot in your system (up to 80 each).
adapter · drone_model · drone_serialstringWhat is flying.
capabilitiesobjectWhat the app detected on the drone (up to 1 KB).
started_atintegerTake-off time in ms.
Response · 201
JSON
{ "session_id": "ses_2mf", "flight_id": "flt_…", "status": "open",
  "live_url": "wss://ubermach.com/api/v1/x3a/bridge/live?session_id=ses_2mf",
  "limits": { "max_alt_m": 120, "geofence": null } }
What can go wrong
StatusWhenWhat to do
402 plan_limitThe client reached its flights for the month (limit: "flights").Tell the pilot; the plan must be upgraded.

Close it after landing with POST /bridge/sessions/{id}/close (ended_at, battery_end_pct, note). Closing twice returns the same summary; only the first close sends session.closed. Send flight events in batches of up to 200 with POST /bridge/sessions/{id}/events (ts, type = log · alert · mode · command · link, data up to 2 KB, optional uid to deduplicate).

Phone connector · Telemetry

Send telemetry

POST/api/v1/ingest/telemetryubd_

Batches of 1 to 400 points (200 recommended). Send session_id so the points go to that flight. Points are idempotent by time: resending a batch after a timeout does not duplicate anything. Points older than 30 days or more than 5 minutes in the future are dropped; out-of-range values are stored as null. The whole point is kept in data (up to 4 KB).

Point
FieldTypeDescription
tsinteger · requiredTime in ms.
lat · lngnumberPosition.
alt_mnumber−500 to 10 000.
speed_ms · heading_degnumber0–100 m/s · 0–360°.
battery_pct · link_qualitynumber0–100.
satellites · modenumber · string0–60 · flight mode (up to 40).
Request
cURL
curl -X POST https://ubermach.com/api/v1/ingest/telemetry \
  -H "Authorization: Bearer ubd_…" -H "Content-Type: application/json" -d '{
  "session_id": "ses_2mf",
  "points": [ { "ts": 1792540811500, "lat": 40.4168, "lng": -3.7038, "alt_m": 42.5,
                "speed_ms": 6.1, "heading_deg": 228, "battery_pct": 81, "satellites": 19,
                "mode": "P-GPS", "link_quality": 96, "gimbal": { "pitch": -30 } } ]
}'
Response · 202
JSON
{ "accepted": 1, "inserted": 1, "duplicates": 0,
  "rejected": { "count": 0, "invalid_ts": 0, "too_old": 0, "in_future": 0, "min_ts": null, "max_ts": null },
  "flight_id": "flt_…", "session_id": "ses_2mf" }

Check rejected before deleting points locally. Points with an invalid time, older than 30 days or more than 5 minutes in the future are not stored; rejected says how many, why and their time range. If every point of the batch was rejected, the answer is 400 no_valid_points with the same detail. With a session, points in the future also leave an alert event with code: "clock_skew".

Phone connector · Live

Publish live

WSwss://ubermach.com/api/v1/x3a/bridge/live?session_id=…ubd_

Use the live_url of the session. Send {"type":"telemetry","point":{…}}, {"type":"event","event":{…}}, {"type":"ping"} and video frames as binary messages (same format as the live channel, up to 40 frames per second and 1 MB per frame). Nothing sent here is stored: keep sending telemetry to ingest/telemetry. One publisher per drone: a new connection replaces the previous one (close code 4000); a closed session or rotated key closes it with 4001.

Phone connector

Missions and commands

Phone connector · Commands

Fetch commands

GET/api/v1/bridge/commandsubd_

Poll every few seconds while the app is connected. Returns up to 20 pending commands (a dispatched mission arrives as mission_start with its waypoints) and marks them sent. Returns home and landings without an answer are sent again after 10 seconds. Answer each one with an ack. Before starting a mission, the app must ask the pilot to confirm the take-off on the remote controller.

Response · 200
JSON
{ "commands": [ { "id": "cmd_…", "type": "mission_start", "mission_id": "msn_9x31",
  "params": { "name": "Roof inspection", "waypoints": [ … ], "ai": { "mode": "describe" } },
  "created_at": … } ] }

POST /api/v1/bridge/commands/{id}/ack with { "status": "done" | "rejected", "detail" }. A done on mission_start moves the mission to running; a rejected sends it back to approved. When the flight ends, call POST /bridge/missions/{id}/complete with status = done or aborted (landed before finishing).

Phone connector · Capabilities

Report capabilities

PUT/api/v1/x3a/bridge/capabilitiesubd_

Send { adapter, capabilities } with what the app detects on the real drone. It replaces what was declared and is only written if it changed.

Response · 200
JSON
{ "device_id": "dev_4k2p", "adapter": "dji_v5", "capabilities": { … }, "changed": true }
Phone connector

Uploads

Phone connector · Uploads

Upload a photo

PUT/api/v1/x3a/bridge/media?…ubd_

The body is the file itself (JPEG, PNG, DNG or a short MP4, up to 50 MB) with its Content-Type; the metadata goes in the query. Idempotent by content: uploading the same file again returns 200 with duplicate: true. Sends media.created and runs the automatic AI if the photo is up to 5 MB. For larger photos, upload a JPEG preview with PUT /bridge/media/{id}/preview.

Query
FieldTypeDescription
session_id · mission_idstringLink the file to the flight and the mission.
external_id · external_form · external_fieldstringWhere it belongs in your system (up to 80 each).
external_filenamestringOriginal file name: letters, numbers, ., _, -, up to 120.
lat · lng · alt_m · heading_deg · gimbal_pitch · taken_at · seqnumberWhere, when and in what order it was taken.
aistringdetect or describe for this photo.
Request
cURL
API=https://ubermach.com/api/v1/x3a
DEV="Authorization: Bearer ubd_…"

curl -X PUT "$API/bridge/media?session_id=ses_2mf&mission_id=msn_9x31&external_filename=IMG_0042.JPG&lat=40.4168&lng=-3.7038&alt_m=35&taken_at=1792540900000" \
  -H "$DEV" -H "Content-Type: image/jpeg" --data-binary @IMG_0042.JPG
Response · 201
JSON
{ "id": "med_7hq", "sha256": "9f2c…", "bytes": 5242880 }
What can go wrong
StatusWhenWhat to do
402 plan_limitThe client is out of storage (limit: "storage").Keep the file on the phone and upload it later.
413 · 415Over 50 MB, or a type that is not accepted.Use a video upload for large videos.
Phone connector · Uploads

Upload a video in parts

POST/api/v1/x3a/bridge/uploadsubd_

For MP4 videos up to 4 GiB, resumable. 1. Start with { content_type: "video/mp4", bytes, sha256, session_id, external_*, taken_at, duration_s }: you get upload_id, media_id, part_size (10 MiB) and total_parts. If an upload of the same sha256 is open, it is returned with parts_done so you can resume; if it is complete, you get complete: true. 2. Send each part with PUT /bridge/uploads/{id}/parts/{n} (from 1, exact Content-Length; repeating a part replaces it). 3. Finish with POST /bridge/uploads/{id}/complete, which creates the file and sends media.created.

If complete answers 409 with missing, upload those parts and call it again. GET /bridge/uploads/{id} shows the progress; DELETE aborts it. The hash is the one the phone declared, so videos come with sha256_verified: false.

Response · 201
JSON
{ "upload_id": "upl_…", "media_id": "med_…", "part_size": 10485760,
  "total_parts": 37, "parts_done": [] }
Phone connector · Detection

Detect objects in a frame

POST/api/v1/x3a/bridge/detectubd_

Real-time detection for the pilot's screen. multipart/form-data with imagen (JPEG up to 2 MB), optional session_id, grupos (comma-separated: personas, livianos, pesados, dos_ruedas) and conf (0.1 to 0.9). Returns the objects with a tracking id and a box normalized 0–1. At most 3 frames per second per drone (429 with Retry-After).

Response · 200
JSON
{ "ms": 84, "ancho": 1280, "alto": 720,
  "objetos": [ { "track_id": 14, "clase": "car", "grupo": "livianos", "nombre": "…",
    "score": 0.88, "box": [0.61, 0.70, 0.66, 0.77] } ],
  "nuevos": [14], "cuadro": 312 }
What can go wrong
StatusWhenWhat to do
502 · 503The detection service did not answer in 4 seconds, or it is not configured.Skip that frame and keep flying.
Full specification

OpenAPI 3.1 · /api/v1/openapi.json

Import it into Postman, Insomnia or your code generator. Questions: contacto@ubermach.com

Download the spec