Appearance
Connect your device
This page is the self-announce path: your payload announces itself to MC. Most payloads should serve the device API instead, and let the bus announce them.
Before you start
Complete Set up a test bench. Your payload has an address on the bus's payload LAN and reaches the Internet. Payload Lab does not test the self-announce path yet; use it only for logs and metrics on this path.
Ask Urban Sky for the following:
| Item | Used for |
|---|---|
| Test API URL | MC_URL, without a trailing slash. |
| Enrolled device token | DEVICE_API_TOKEN; identifies the device's organization. |
| The bus's serial | identity.parentSerial, so MC reaches your command server through that bus. balto payloads list shows it. |
| An Urban Sky account | Balto, and the operator steps (adopt, enable commands, send). See Test with Balto. |
Use Bash or zsh, curl 7.76+ (for --fail-with-body), and jq. Keep credentials out of committed files.
The operator steps in this guide use balto payloads, which signs in as you. If you call the HTTP API instead, set OPERATOR_TOKEN to a signed-in operator's access JWT in the same organization. An SDK/API token or the device token does not work for operator requests.
1. Understand the routes
| Request | Route | Authentication |
|---|---|---|
| Device → MC | HTTPS to MC_URL, out through the bus's Internet link. | x-device-api-token. |
| Operator → MC | HTTPS to MC_URL, from Balto or your own client. | Your Urban Sky sign-in, or a bearer token. |
| MC → device | Through the bus link to your address on the payload LAN. Your device needs no public address. | The bus link is mutual TLS between MC and the bus. Add mTLS on your command server if you want it end to end. |
Announce your address on the payload LAN (for example 192.168.8.20 on a Husky bus) and name the bus in identity.parentSerial. MC finds your lease on that bus and dials your command server through the bus link. Bind the command server to the payload-LAN interface.
Loopback, unspecified, link-local/metadata, and multicast addresses are rejected. Other network modes, for a payload that does not fly on a Husky bus, are in the API reference.
2. Create the first announcement
Save as announce.json. Replace the identity and address. Start with telemetry only; add commands after their handlers work.
json
{
"schemaVersion": 1,
"identity": {
"serial": "ENV-001",
"name": "Environmental sensor",
"vendor": "Example Instruments",
"parentSerial": "uskybus-8901fc"
},
"reachability": {
"address": "192.168.8.20",
"port": 8091,
"protocol": "http",
"networkMode": "NETWORK_MODE_SHARED_VPN"
},
"codec": "CODEC_JSON",
"security": "SECURITY_TRANSPORT_VPN",
"telemetryManifest": {
"schemaVersion": 1,
"staleAfterS": 30,
"fields": [
{
"key": "sensor.temperature_c",
"label": "Temperature",
"type": "FIELD_TYPE_NUMBER",
"unit": "°C",
"min": -80,
"max": 125
},
{
"key": "sensor.sample_period_s",
"label": "Applied sample period",
"type": "FIELD_TYPE_NUMBER",
"unit": "s",
"min": 1,
"max": 60
},
{
"key": "sensor.state",
"label": "Sensor state",
"type": "FIELD_TYPE_ENUM",
"values": ["idle", "sampling", "fault"]
}
]
},
"commandManifest": {
"schemaVersion": 1,
"commands": []
}
}Keep the serial stable across restarts: 1–64 letters, digits, _, ., :, or -. Use the exact property names and enum strings shown. Every announcement needs at least one telemetry field or command.
Set address to the address your payload has on the payload LAN, and parentSerial to your bus's serial. On a cloud bench bus the address is 10.55.0.x. NETWORK_MODE_SHARED_VPN is the mode for a payload behind a bus; the name is older than the bus link. With SECURITY_TRANSPORT_VPN and "protocol": "http", your command server needs no certificate: the bus link carries the command with mutual TLS as far as the bus. For end-to-end mTLS, use SECURITY_TRANSPORT_MTLS and "protocol": "https", and ask Urban Sky for a device certificate.
Re-announce when DHCP gives your payload a new address.
Optional: say what the device is
Without these fields, MC shows identity.name. Add them to identity to give operators a clear label:
json
"identity": {
"serial": "ENV-001",
"name": "Environmental sensor",
"vendor": "Example Instruments",
"displayName": "ENV-001 sensor",
"kind": "env-sensor",
"model": "ENV-1",
"softwareVersion": "1.4.2",
"hardwareRevision": "rev C",
"description": "Temperature sensor with a configurable sampling interval"
}| Field | Stored limit |
|---|---|
displayName, model | 80 characters. |
kind | 32 characters: lowercase letters, digits, _, and -. |
softwareVersion, hardwareRevision | 64 characters. |
description | 200 characters. |
MC trims and shortens values that are too long. MC drops a kind with other characters. It does not reject the announcement. These fields are labels only. They do not change permissions or routing. Update softwareVersion with each release.
Optional: declare other network services
If the device serves more than its command server and video, list each service in services, for example an SSH server:
json
"services": [{ "name": "ssh", "port": 22, "protocol": "ssh" }]Use up to 16 services. Each name is unique and matches ^[a-z0-9][a-z0-9._-]{0,31}$. A declaration grants no access by itself. Send the full list in every announcement. An announcement without services removes the stored list.
3. Send the announcement
Replace the placeholders below. Use the API origin, without a trailing slash. Stop at any error before continuing.
bash
MC_URL='https://YOUR_API_HOST'
DEVICE_API_TOKEN='YOUR_ENROLLED_DEVICE_TOKEN'
OPERATOR_TOKEN='YOUR_OPERATOR_ACCESS_JWT'Read the serial from your announcement and submit it:
bash
PAYLOAD_SERIAL=$(jq -er '.identity.serial' announce.json) &&
curl --fail-with-body --silent --show-error \
-X POST "${MC_URL:?Set MC_URL}/payloads/announce" \
-H "x-device-api-token: ${DEVICE_API_TOKEN:?Set DEVICE_API_TOKEN}" \
-H 'Content-Type: application/json' \
--data-binary @announce.json > announce-response-next.json &&
PAYLOAD_ID=$(jq -er '.id' announce-response-next.json) &&
mv announce-response-next.json announce-response.json &&
jq . announce-response.jsonExpected: HTTP 200 with a UUID id, serial matching your announcement, status: "discovered", parent_serial: null, and relays: null.
Re-announcing with the same credential and serial updates the address and supplied manifests, preserving the UUID and adoption status. Another credential cannot claim that serial. If you already registered it, expect its existing status rather than discovered.
commandsEnabled, telemetrySchemaUrl, and commandsUrl belong to operator create/update requests, not announcements.
Keep announce.json, announce-response.json, and the shell variables for the next page. In a new shell, recover the UUID with PAYLOAD_ID=$(jq -er '.id' announce-response.json).
Check it worked
List the bus's payloads in Balto. Your serial appears with status: "discovered":
bash
balto payloads list --bus uskybus-8901fcIn the Balto app (balto ui), open the bus and choose the Payloads tab. Your payload is listed under the bus.
If it fails
401: check the device token and API URL.403: ask Urban Sky to resolve the existing registration's ownership.400: check the address, field names, enum values, and manifests.- Not in
balto payloads list: check that you used the same organization's device token, and thatparentSerialis the bus's serial. See Troubleshooting.