Skip to content

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 globally

UrbanSkySDK Class ​

Static Initialization Method ​

javascript
await UrbanSkySDK.init(config)

Initializes and connects to the Urban Sky SDK with the provided configuration.

Parameters:

ParameterTypeDescription
configObjectSDK 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:

ParameterTypeDescription
eventstringEvent type to listen for
handlerFunctionFunction 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:

ParameterTypeDescription
eventstringEvent type
handlerFunctionSpecific 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 event

Configuration ​

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.

PropertyTypeDefaultDescription
apiTokenstringrequiredYour Urban Sky API token
baseUrlstring'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:

CodeDescription
'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.js
javascript
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)
OptionDefaultMeaning
busUrlUSKY_BUS_INGEST_URL, else the bus with a LAN route (see below)The bus's ingest address on the payload LAN.
flushIntervalMs2000Milliseconds between log sends.
metricIntervalMs10000Milliseconds between metric sends.
maxQueuedLines2000Lines kept in memory while the bus does not take them. The oldest go first.
timeoutMs3000Milliseconds for one request to the bus.
transportNode httpReplaces the HTTP call, for tests. See BusTransport.
starttrueStarts 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 in attrs.
  • 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 ​

MemberMeaning
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: numberLines the client dropped: queue full, rate drops the bus reported, or a body the bus refused.
lastResult: PayloadIngestResult | nullThe bus's last reply: { accepted, dropped, reasons }.
busUrl: stringThe address in use.

Queue, pacing and drops ​

SituationWhat the client does
NormalSends logs every flushIntervalMs, at most 64 KiB per request. Sends metric values every metricIntervalMs.
A backlogPaces 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-AfterKeeps the lines and sends nothing until Retry-After has passed (1 to 60 seconds).
200 with rate or bus_rate dropsThe 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 unreachableKeeps the lines and tries again after max(flushIntervalMs, 5000) milliseconds.
Queue fullDrops 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 ​