Downlink Commands
Endpoint
POST /api/v1/open/downlink/commands— Accept a cloud-to-device command and return the platform acceptance result.
Interaction Model
001 implements synchronous acceptance and asynchronous dispatch event creation:- The integrator submits a command.
- The platform verifies the signature, nonce, tenant binding, Casbin authorization, request body, and idempotency key.
- The platform writes the command fact and dispatch event in one PostgreSQL transaction.
- The platform returns
command_id,ACCEPTED,accepted_at, andrequest_id. - After the transaction commits, the API service attempts to publish the NATS event
tenant.<tenant_id>.command.accepted.
202 Accepted only means the platform accepted and persisted the command. It does not mean the device executed it successfully.
Command Events and Status Model
Open Platform models subscribable events separately from command statuses. The currently subscribable command events are:| event_type | Meaning |
|---|---|
command.accepted | The platform authenticated, authorized, idempotency-checked, and persisted the command. |
command.completed | The adapter or device returned a terminal result. The event payload carries the concrete execution result. |
command.problem | The adapter found a command-boundary diagnostic problem that cannot safely be correlated to a concrete command, such as a malformed accepted-command payload. |
command.completed payload and the command query response. command.problem is not a normal command failure; UTMOS keeps the raw diagnostic payload as a device recent event.
The command query response exposes the aggregate status as public_status:
| public_status |
|---|
ACCEPTED |
DISPATCHING |
DELIVERED |
RUNNING |
SUCCEEDED |
FAILED |
TIMED_OUT |
UNSUPPORTED |
CANCELLED |
UNKNOWN |
adapter_status:
| adapter_status |
|---|
RECEIVED |
REJECTED_BY_ADAPTER |
SENT_TO_DEVICE |
TRANSPORT_FAILED |
RESPONSE_TIMEOUT |
device_execution_status:
| device_execution_status |
|---|
ACCEPTED_BY_DEVICE |
SUCCEEDED |
TEMPORARILY_REJECTED |
DENIED |
UNSUPPORTED |
TIMED_OUT |
command.completed.status:
| status |
|---|
rejected_by_agent |
sent_to_px4 |
ack_accepted |
ack_temporarily_rejected |
ack_denied |
ack_unsupported |
ack_timeout |
transport_failed |
parameter_value_confirmed |
parameter_error |
parameter_response_mismatch |
parameter_timeout |
adapter_status, device_execution_status, and public_status in the query response.
PX4 adapter query responses also include these fields in adapter_result:
| Field | Description |
|---|---|
human_summary | Human-readable terminal summary generated by the agent. Chinese is preferred when available. |
hardware_response | Typed PX4 hardware response payload. MAV_CMD flows use COMMAND_ACK; parameter read/set flows use PARAM_VALUE or PARAM_ERROR; mission, FTP, and timesync flows use MISSION_ACK, FILE_TRANSFER_PROTOCOL, and TIMESYNC typed payloads when integrated. |
source_evidence | Local PX4/MAVLink source evidence used by the agent for the response mapping. |
Authentication
- Open Platform signature authentication is required:
X-Api-Id,X-Api-Timestamp,X-Api-Nonce, andX-Api-Signature. X-Request-Idis optional. The platform generates one when omitted and returns it in responses and logs.- Creating a command requires tenant-domain authorization:
open:command:create. - See Authentication & Signing for a signature example.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| vendor | string | yes | Vendor identifier, e.g. dji |
| device_id | string | yes | Target device ID |
| command_type | string | yes | Command type from the platform-supported catalog |
| payload | object | yes | Command payload |
| idempotency_key | string | yes | Idempotency key |
| timeout_seconds | integer | no | Timeout in seconds; defaults to 30, valid range 1~300 |
payload is not an arbitrary object. It must match the command protocol for the selected vendor and command_type. For the full DJI payload field catalog, JSON examples, and code samples, see Command Payloads.
Responses
202 Accepted— New command accepted.200 OK— Idempotent replay hit; returns the existing command.4xx/5xx— Standard error envelope.
Idempotency
- The idempotency scope is the authenticated
tenant_id + client_id + idempotency_key. - Replaying the same business request in that scope returns the same
command_id. - Reusing the same idempotency key with different business semantics returns
IDEMPOTENCY_CONFLICT. - The semantic hash includes
vendor,device_id,command_type, canonicalizedpayload, and normalizedtimeout_seconds. - The semantic hash does not include
X-Request-Id,X-Api-Nonce,X-Api-Timestamp, or signature headers.
Persistence and Publication Semantics
The acceptance response means the platform wrote the command fact and dispatch event in one PostgreSQL transaction. After commit, the API service attempts to publish:ACCEPTED. The platform keeps the dispatch event and failure reason for the recovery flow.
Common Errors
| code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid authenticated identity |
SIGNATURE_INVALID | 401 | Signature mismatch |
TIMESTAMP_EXPIRED | 401 | Request timestamp is outside the allowed skew window |
NONCE_REPLAYED | 401 | Nonce was reused within the replay window |
FORBIDDEN | 403 | Client is not authorized in the tenant domain |
TENANT_NOT_FOUND | 404 | No active tenant could be resolved |
TENANT_BINDING_INVALID | 403 | Client-to-tenant binding is invalid |
INVALID_REQUEST_BODY | 400 | JSON request body is malformed |
VALIDATION_FAILED | 400 | Required field missing, invalid type, or out-of-range value |
UNSUPPORTED_VENDOR | 400 | Vendor is not registered in the platform command catalog |
UNSUPPORTED_COMMAND | 400 | Command type is unsupported or outside trusted capability scope |
IDEMPOTENCY_CONFLICT | 409 | Same idempotency key was used for different command semantics |
INTERNAL_ERROR | 500 | Platform internal error |
Example
The samples below show the request structure. Production clients must calculateX-Api-Signature as documented in Authentication & Signing.