Appearance
JavaScript SDK API Reference
Complete API reference for the Urban Sky JavaScript SDK for Node.js applications.
Installation
Load the SDK using our CDN loader:
javascript
// Load the SDK dynamically
const response = await fetch('https://sdk.atmosys.com/runtime/js/current/loader.js')
const loaderCode = await response.text()
eval(loaderCode) // UrbanSkySDK is now available globallyUrbanSkySDK Class
Static Initialization Method
javascript
await UrbanSkySDK.init(config)Initializes and connects to the Urban Sky SDK with the provided configuration.
Parameters:
| Parameter | Type | Description |
|---|---|---|
config | Object | SDK configuration object |
Returns: Promise that resolves to the initialized and connected SDK instance
Example:
javascript
// UrbanSkySDK is available globally after loading the loader
const sdk = await UrbanSkySDK.init({
apiToken: 'your-api-token',
// Most other options have sensible defaults and don't need to be set
})Methods
connect()
Note
The connect() method is automatically called by UrbanSkySDK.init(). You typically don't need to call this manually.
javascript
async connect()Establishes connection to the Urban Sky telemetry feed.
Returns: Promise that resolves when connected
Throws: SDKError if connection fails
Example:
javascript
// Usually not needed - init() handles connection automatically
try {
await sdk.connect()
console.log('Connected successfully')
} catch (error) {
console.error('Connection failed:', error)
}disconnect()
javascript
disconnect()Disconnects from the telemetry feed and cleans up resources.
Example:
javascript
sdk.disconnect()on(event, handler)
javascript
on(event, handler)Registers an event handler for SDK events.
Parameters:
| Parameter | Type | Description |
|---|---|---|
event | string | Event type to listen for |
handler | Function | Function to call when event occurs |
Events:
'balloon:update'- Balloon telemetry update received'unassigned:devices'- Unassigned device telemetry received'connected'- Successfully connected to telemetry feed'disconnected'- Disconnected from telemetry feed'connecting'- Attempting to connect'failed'- Connection failed'error'- Error occurred
Example:
javascript
sdk.on('balloon:update', update => {
console.log('Balloon update:', update)
})
sdk.on('error', error => {
console.error('SDK error:', error)
})off(event, handler)
javascript
off(event, handler)Removes an event handler. If no handler is provided, removes all handlers for the event.
Parameters:
| Parameter | Type | Description |
|---|---|---|
event | string | Event type |
handler | Function | Specific handler to remove (optional) |
Example:
javascript
const handler = update => console.log(update)
sdk.on('balloon:update', handler)
sdk.off('balloon:update', handler) // Remove specific handler
sdk.off('balloon:update') // Remove all handlers for this eventConfiguration
SDKConfig
javascript
{
apiToken: string, // Required: Your Urban Sky API token
baseUrl?: string // Optional: Custom API URL (rarely needed)
}Note: Most configuration options have sensible defaults. You typically only need to provide your API token.
| Property | Type | Default | Description |
|---|---|---|---|
apiToken | string | required | Your Urban Sky API token |
baseUrl | string | 'https://api.ops.atmosys.com' | API base URL (rarely needs to be changed) |
Data Types
BalloonUpdate
javascript
{
balloonId: string,
missionId: string,
devices: Device[]
}Device
javascript
{
deviceId: string,
deviceType: string, // e.g., "PLD" (Payload), "APX" (Apex), "BLS" (Ballaster)
lat: number, // Latitude in decimal degrees
lng: number, // Longitude in decimal degrees
altitude?: number, // Altitude in meters (when available)
timestamp: string // ISO 8601 timestamp
}Error Types
SDKError
javascript
{
code: string,
message: string,
details?: any
}Error Codes:
| Code | Description |
|---|---|
'AUTH_INVALID_TOKEN' | API token is invalid or expired |
'AUTH_INSUFFICIENT_PERMISSIONS' | Token lacks SDK permissions |
'CONNECTION_FAILED' | Connection to server failed |
'NETWORK_ERROR' | Network connectivity issue |
'SERVICE_UNAVAILABLE' | Urban Sky service temporarily unavailable |
PayloadTelemetry (payload logs and metrics)
PayloadTelemetry runs on a payload. It sends logs and metrics to the Husky bus that the payload rides on. The bus labels them with your payload and forwards them to Urban Sky. It needs no API token, key or certificate: the bus knows your payload from its address on the payload LAN. For background, see Run on a Husky bus.
It is a single file with no dependencies (Node.js 18+). It does not need centrifuge. The CDN loader does not expose it, so put the file into your payload software. Use payload-telemetry.js for CommonJS or payload-telemetry.mjs for ES modules:
bash
SDK_VERSION=$(curl -fsSL https://sdk.atmosys.com/runtime/js/current/version.txt)
curl -fsSL "https://sdk.atmosys.com/runtime/js/${SDK_VERSION}/payload-telemetry.js" -o urbansky-payload.jsjavascript
const { PayloadTelemetry } = require('./urbansky-payload.js')The same class is also exported from the SDK bundle (index.js), next to UrbanSkySDK.
Constructor
typescript
new PayloadTelemetry(options?: PayloadTelemetryOptions)| Option | Default | Meaning |
|---|---|---|
busUrl | USKY_BUS_INGEST_URL, else the bus with a LAN route (see below) | The bus's ingest address on the payload LAN. |
flushIntervalMs | 2000 | Milliseconds between log sends. |
metricIntervalMs | 10000 | Milliseconds between metric sends. |
maxQueuedLines | 2000 | Lines kept in memory while the bus does not take them. The oldest go first. |
timeoutMs | 3000 | Milliseconds for one request to the bus. |
transport | Node http | Replaces the HTTP call, for tests. See BusTransport. |
start | true | Starts the send timer. With false, nothing is sent until you call flush(). |
The timer is unref()'d, so it does not keep your process alive. Call await telemetry.close() before you exit to send what is still queued.
Log methods
log(level, message, options?): void
Queues one log line and returns at once.
level:'debug','info','warn'or'error'.'warning'counts as'warn'. Any other value is sent as'info'.message: the text. The bus replaces digits in it with#, so put numbers inattrs.options.event: an optional snake_case event name, such as'camera_frame_drop'. A line with an event is forwarded at any level.options.attrs: an optional flat object of strings, numbers and booleans. Only the first 32 keys are kept.
The client stamps each line with the payload's time. A line over 8 KiB is sent with its message cut to 1,024 characters and with no attrs.
debug(message, options?), info(...), warn(...), error(...)
These call log() with that level.
javascript
telemetry.warn('frame drop', { event: 'camera_frame_drop', attrs: { dropped: 12, stream: 'eo' } })
telemetry.error('gimbal stall', {
event: 'gimbal_stall',
attrs: { axis: 'az', request_id: requestId },
})Metric methods
Declare each metric for your payload first. The bus drops undeclared names, attribute keys and attribute values. Use the name without a prefix: the bus adds payload_<kind>_.
declareMetrics(metrics)
For a payload with no :8090 capability document. metrics is the same list as that document's metrics: each entry has name, type (GAUGE or COUNTER), and optional unit, description and attributes ([{"key": ..., "values": [...]}]). The client sends it with PUT /api/v1/payload/metrics/declarations before the next metrics, and again whenever the bus answers undeclared, because the bus keeps the list in memory only.
resolveBusUrl(busUrl?, routeTable?): string
The address the client uses: the argument, else USKY_BUS_INGEST_URL, else the bus address (192.168.8.1, then 10.55.0.1) that the payload has a LAN route to in /proc/net/route, else http://192.168.8.1:4318.
gauge(name, value, attrs = {}): void
Sets a GAUGE's current value for one series (name plus attributes). Only the latest value is sent.
count(name, by = 1, attrs = {}): void
Adds by to a COUNTER. The client keeps the running total and sends the total, because the bus expects a cumulative value. The total starts again at 0 when your process restarts.
javascript
telemetry.gauge('temp_c', 54.5, { sensor: 'cpu' })
telemetry.count('commands_failed')
telemetry.count('frames_dropped', 12, { stream: 'eo' })Sending and status
| Member | Meaning |
|---|---|
flush(): Promise<void> | Sends the queued lines and the current metric values now. It still obeys the pacing and back-off below. |
close(): Promise<void> | Stops the timer, then flushes once. |
status(): Promise<Record<string, unknown> | null> | GET /api/v1/payload/status: your payload.id, payload.kind, admitted and refused metrics, and drop counts. null if the bus does not answer. |
dropped: number | Lines the client dropped: queue full, rate drops the bus reported, or a body the bus refused. |
lastResult: PayloadIngestResult | null | The bus's last reply: { accepted, dropped, reasons }. |
busUrl: string | The address in use. |
Queue, pacing and drops
| Situation | What the client does |
|---|---|
| Normal | Sends logs every flushIntervalMs, at most 64 KiB per request. Sends metric values every metricIntervalMs. |
| A backlog | Paces itself under the bus's log cap: 448 KiB at once, then 60 KiB per minute. The rest waits in the queue. |
429 with Retry-After | Keeps the lines and sends nothing until Retry-After has passed (1 to 60 seconds). |
200 with rate or bus_rate drops | The bus took part of the batch. The client counts the rest in dropped and waits 10 seconds. |
Any other HTTP error (400, 403, 413) | Counts the lines in dropped. Does not retry them. |
| Bus unreachable | Keeps the lines and tries again after max(flushIntervalMs, 5000) milliseconds. |
| Queue full | Drops the oldest line and counts it in dropped. |
A send error never rejects into your code from the timer. flush() and close() resolve even when the bus does not answer.
The bus's own limits are in Limits. All payloads on one bus share 256 KiB/min of logs, so another payload can use part of your budget. The bus_rate back-off covers that.
See Send payload logs and metrics for a complete example.
Complete Example
javascript
const fetch = require('node-fetch') // Ensure node-fetch is installed
class BalloonTracker {
constructor(apiToken) {
this.apiToken = apiToken
this.sdk = null
this.balloons = new Map()
}
async initialize() {
// Load the SDK
const response = await fetch('https://sdk.atmosys.com/runtime/js/current/loader.js')
const loaderCode = await response.text()
eval(loaderCode)
// Initialize and connect SDK
this.sdk = await UrbanSkySDK.init({
apiToken: this.apiToken,
})
this.setupEventHandlers()
}
setupEventHandlers() {
this.sdk.on('balloon:update', this.handleBalloonUpdate.bind(this))
this.sdk.on('connected', this.handleConnected.bind(this))
this.sdk.on('disconnected', this.handleDisconnected.bind(this))
this.sdk.on('error', this.handleError.bind(this))
}
handleBalloonUpdate(update) {
console.log(`Balloon ${update.balloonId} update:`)
update.devices.forEach(device => {
console.log(` Device ${device.deviceId} (${device.deviceType}):`)
console.log(` Location: ${device.lat}, ${device.lng}`)
if (device.altitude) {
console.log(` Altitude: ${device.altitude}m`)
}
console.log(` Time: ${new Date(device.timestamp).toLocaleString()}`)
})
this.balloons.set(update.balloonId, update)
}
handleConnected() {
console.log('✅ Connected to Urban Sky telemetry feed')
}
handleDisconnected() {
console.log('🔌 Disconnected from telemetry feed')
}
handleError(error) {
console.error('🚨 SDK Error:', error.message)
switch (error.code) {
case 'AUTH_INVALID_TOKEN':
console.error('Please check your API token')
break
case 'CONNECTION_FAILED':
console.error('Network connectivity issue')
break
default:
console.error('Unexpected error:', error.details)
}
}
async start() {
try {
await this.initialize()
// SDK is already connected via init()
console.log('Tracker started successfully')
} catch (error) {
console.error('Failed to start tracker:', error)
throw error
}
}
stop() {
if (this.sdk) {
this.sdk.disconnect()
}
}
getBalloon(balloonId) {
return this.balloons.get(balloonId)
}
getAllBalloons() {
return Array.from(this.balloons.values())
}
}
// Usage
const tracker = new BalloonTracker('your-api-token')
tracker
.start()
.then(() => {
console.log('Balloon tracker started')
})
.catch(error => {
console.error('Failed to start tracker:', error)
})
// Graceful shutdown
process.on('SIGINT', () => {
console.log('Shutting down...')
tracker.stop()
process.exit(0)
})Testing Your Implementation
You can test your SDK integration using our test endpoint:
javascript
// Send a test balloon update to verify your implementation
const testResponse = await fetch('https://api.ops.atmosys.com/sdk/test/balloon', {
method: 'POST',
headers: {
'x-api-token': 'your-api-token',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
})
if (testResponse.ok) {
console.log('Test message sent - you should receive it via your SDK listener')
}Next Steps
- Imagery SDK Guide - Access mission imagery and photos
- Python SDK Reference - Python SDK documentation
- Send payload logs and metrics -
PayloadTelemetryon a payload - Basic Usage Examples - Practical usage examples
- Error Handling Guide - Comprehensive error handling