These two blueprints are the persistence layer for flight plans. They store waypoints and fence vertices in PostgreSQL in a MAVLink-compatible shape, but they do not talk to a drone — uploading a stored mission/fence to a vehicle is a separate /drone/action/* call documented in Drone Management & Control Actions. Think of this page as “the database side of the map editor.”
  • src/routes/mission_routes.py — mission + mission-point CRUD (9 endpoints), backed by MissionService (src/service/mission_service.py) and PointService (src/service/point_service.py).
  • src/routes/geofence_routes.py — geofence + geofence-point CRUD/toggle/batch (11 endpoints), backed by GeofenceService (src/service/geofence_service.py).
Every endpoint on this page is @jwt_required() (JWT Bearer, the UI auth model in HTTP API Overview). Unlike Socket.IO telemetry rooms, these routes are genuinely user-scoped: every service query filters user_id == get_jwt_identity(), so a token can only see and mutate its own missions and geofences.

Lifecycle at a glance

The stored rows are MAVLink-shaped so the upload path (push_mission, push_geofence, sync_geofences in src/service/drone_control_service.py) can convert each row to a rosbridge waypoint with almost no transformation. The exact conversion and the on-drone protocol live in Mission & Geofence MAVLink Format.
The CLAUDE.md “Mission Format” note claims the backend auto-generates a reversed return path plus a trailing RTL (command 20) and a speed row (command 178). That is stale. push_mission (src/service/drone_control_service.py:784) uploads your stored waypoints as-is and only prepends a synthetic TAKEOFF (command 22) when the first waypoint isn’t already a takeoff. Nothing reverses the path or appends RTL. Do not reintroduce that behavior when editing.

Missions API

All routes are under the /api/v1 base path. ¹ The GET /missions error branch returns jsonify(error=...) with no status code, so a failure serializes the error body with an HTTP 200 (src/routes/mission_routes.py:323) — a latent bug to preserve/fix deliberately. ² 400 only when the DB raises a foreign-key violation (drone_mission_id_fkey).
Mission point mutations return 201 for create and update and delete and batch. That is not a typo in this doc — it is what the handlers actually return. On the geofence side only point create returns 201 (same as mission); point update, delete, and batch return 200. Any client (and any refactor) must treat both as success; do not “normalize” these codes without updating the Dashboard.

Create a mission, then add points

1

Create the mission shell

POST /mission takes just a name and returns the new id. MissionService.create_mission enforces a UNIQUE(user_id, name) constraint (src/models/mission.py:19).
2

Add points one at a time

POST /mission/point expects a JSON array, but the handler only reads element [0] (src/routes/mission_routes.py:138). Sending several points in one call silently persists only the first.
The response is the whole parent mission (id, name, and the full mission_points array), not just the created point.

Mission point fields

create_mission_point (src/service/mission_service.py:106) reads the keys below. The underlying mission_point table columns and their nullability come from src/models/mission_point.py.
The Swagger block for POST /mission/point documents an order property, but the handler and service actually read sequence — there is no order mapping. Because sequence is NOT NULL, sending order instead of sequence (or omitting type/altitude) hits a DB NOT NULL/enum violation and surfaces as a bare 500. Trust the code (sequence), not the Swagger.

Batch update (reorder / drag-to-edit)

PATCH /mission/<mission_id>/points is what the editor calls after a drag-reorder. The body is an array of {id, ...} objects and PointService.batch_update (src/service/point_service.py:47) passes each raw dict straight into MissionPoint.query...update(point_data).
Because the raw request dict is handed to SQLAlchemy’s .update(), every key must be a real mission_point column (id, lat, lng, altitude, type, label, sequence, frame, command, param1–param4). An unknown key such as order raises and the whole batch fails with 500. Each row is also re-scoped by user_id, so you can only touch your own points.

Deleting

DELETE /mission/<mission_id> (MissionService.delete, src/service/mission_service.py:83) does three things in order: sets Drone.mission_id = NULL on any drone pointing at this mission, deletes the mission’s points, then deletes the mission. The route still wraps a foreign-key violation into a friendly 400 (“unassign it first”) in case a drone reference survives.

Geofences API

Geofences model MAVLink fence zones — polygons and circles, either inclusion (stay inside) or exclusion (stay outside).
PATCH /geofence/<id>/toggle exists so the “enable this fence” checkbox does not need to round-trip the whole object. sync_geofences on the drone side only uploads geofences whose enabled is true, so the toggle is the switch that decides whether a fence actually reaches the aircraft. See Drone Management & Control Actions.

Geofence metadata fields

Set on POST /geofence and PATCH /geofence/<id> (src/service/geofence_service.py:78; columns in src/models/geofence.py):

Geofence point fields

create_geofence_point (src/service/geofence_service.py:94) reads:
command is NOT NULL in the geofence_point table but the service supplies no default (command=point_details.get("command")), so omitting it 500s. In practice the exact value you store barely matters: on upload, push_geofence / sync_geofences overwrite each point’s command with get_fence_command(type, fence_type) and recompute param1 (src/service/drone_control_service.py:892, :1009). Supply a placeholder (e.g. 5002) to persist; the real command is derived from the parent geofence’s type + fence_type at push time.

Batch update

PATCH /geofence/<id>/points mirrors the mission batch endpoint — GeofenceService.batch_update_points (src/service/geofence_service.py:192) passes each raw dict into .update(), so keys must be real geofence_point columns (id, lat, lng, sequence, frame, command, param1–param4) and rows are re-scoped by user_id. These are the values the fields above map onto. The upload layer converts each row via to_mavlink_waypoint() / to_mavlink_fence_item(), renaming lat → x_lat, lng → y_long, altitude → z_alt (fence points force z_alt = 0.0, they are 2D).

Mission commands (command)

  • 16 — NAV_WAYPOINT
  • 22 — NAV_TAKEOFF (auto-prepended if missing)
  • 21 — NAV_LAND
  • 20 — NAV_RETURN_TO_LAUNCH
Frames: 0 = GLOBAL, 3 = GLOBAL_RELATIVE_ALT.

Fence commands (command)

  • 5001 — POLYGON_VERTEX_INCLUSION
  • 5002 — POLYGON_VERTEX_EXCLUSION
  • 5003 — CIRCLE_INCLUSION (param1 = radius)
  • 5004 — CIRCLE_EXCLUSION (param1 = radius)
Derived by get_fence_command() in src/models/geofence_point.py.

Gotchas worth preserving

POST /mission/point and POST /geofence/point both accept a JSON array but process only data[0] (mission_routes.py:138, geofence_routes.py:154). The batch PATCH .../points endpoints are the real multi-point path. If a future editor wants to add many points in one request, it must loop the single-create endpoint or extend the handler — the array shape is a historical leftover, not multi-insert support.
Adding, updating, or deleting a single point returns the entire mission/geofence object with its refreshed *_points array — not the point you touched. This lets the editor replace its local state in one shot, but means the response can be large for big plans.
Unlike the newer blueprints (auth, executions, billing), mission/geofence routes return bare jsonify payloads (the object itself, or {"error": "..."} / {"message": "..."}), not the {"success": true, "data": ...} envelope from src/utils/common_helper.py. Clients must not assume a uniform envelope across the gateway. See HTTP API Overview.
Mission point create/update/delete/batch all return 201. On the geofence side only point create returns 201 (same as mission); point update/delete/batch return 200. GET /missions even returns 200 on error. These are load-bearing for the current Dashboard — treat any code < 400 as success and change codes only alongside the frontend.

Drone Management & Control Actions

push_mission, push_geofence, clear_fence, sync_geofences, start_mission — the endpoints that actually send a stored plan to a vehicle.

Mission & Geofence MAVLink Format

Full DB → MAVLink conversion, the TAKEOFF auto-prepend, and the on-drone fence/mission protocol.

HTTP API Overview & Auth Models

Base path, the three auth models, and the response-envelope conventions.

Mission & Waypoint Planning (Dashboard)

How the Angular editor builds these payloads and drives the batch-reorder calls.