Decentralized Space Network API
Overview
The Decentralized Space Network API provides programmatic access to satellite pass prediction, reservation management, mission profile management, and file-transfer metadata for ground-station operations.
The API is intended for server-to-server integrations. File payloads are exchanged through S3 buckets; the API stores and reports metadata, ownership, reservation status, and delivery state.
Base path:
/orbapiOrganizations and Administrative UI
API tokens and operational resources belong to organizations. Registration creates a personal organization, and users may join company organizations. The active organization supplies the ownership context for tokens, reservations, storage paths, TLE selection, and transceiver selection.
Roles are owner, admin, operator, and viewer. Profile -> Organizations manages organizations, active selection, and registered-user memberships. Mission Administration lets platform administrators view reservations grouped by organization, edit non-terminal reservation types, and cancel eligible reservations. Assets Management is available to platform administrators and organization owners/admins.
The catalog editor exposes only rows where source = manual, pinned = true, and the row belongs to the selected organization. The same owned manual satellite, TLE, and transceiver records can be listed and edited with a dsn_live_... token carrying catalog:write. Platform-admin booking-edit routes remain internal. Pass windows require cancel-and-recreate.
Authentication
Client operational endpoints use API-token authentication.
Authorization: Bearer dsn_live_...API tokens are scoped. A request is rejected if the token does not include the scope required by the endpoint.
Station agents use station-scoped bearer tokens:
Authorization: Bearer dsn_station_<stationId>_<secret>Station tokens are bound to one ground station. A token for station 1234 cannot poll, refresh upload URLs, or report telemetry for station 3886.
Satellite Identity
Satellite-specific operational requests require satelliteId.
noradId / norad_cat_id is search metadata, not the booking/pass identity. A NORAD lookup can return multiple satellites.
Client flow:
1. Search/list satellites by NORAD or name. 2. Select the intended satellite. 3. Use its satelliteId for pass search, booking, TLE, transceiver, and mission-profile calls.
Normal client tokens cannot override ownerId. Admin/service calls may pass ownerId when acting on behalf of a client.
Satellite ownership is enforced by default through SatelliteOwner.satelliteId + ownerId.
Common scopes:
| Scope | Purpose |
|---|---|
passes:read | Search satellite passes |
bookings:create | Create bookings and attach uplink files |
bookings:cancel | Cancel bookings |
mission-profile:create | Create, read, and delete mission profiles |
catalog:write | List and edit the organization's owned manual catalog |
payloads:write | Upload and manage mission payload files |
payloads:write | Create uplink payload records and receive presigned upload URLs |
Conventions
All request and response bodies are JSON unless otherwise stated.
Timestamps use ISO 8601 UTC format:
2026-05-06T10:00:00ZErrors use the following general shape:
{
"error": "Human-readable error message"
}Common status codes:
| Status | Meaning |
|---|---|
200 | Request completed successfully |
201 | Resource created successfully |
400 | Invalid request |
401 | Missing or invalid API token |
403 | API token does not have the required scope |
404 | Resource not found or not visible to the caller |
500 | Internal server error |
Satellite Pass Search
Searches predicted visibility passes for one satellite across one or more ground stations.
POST /orbapi/passes/search
Authorization: Bearer dsn_live_...Purpose: Calculate upcoming visibility windows for a satellite from one or more ground stations.
Required scope:
passes:readThe request is per satellite and requires the stable DSN satelliteId. A raw TLE may be supplied for a satellite owned by the token's organization. It must contain two correctly aligned 69-character lines with valid checksums. A valid newer TLE updates the organization's selected manual TLE row, so subsequent frontend, scheduler, and API reads use it too.
Search by satellite ID:
{
"satelliteId": "sat_123",
"days": 7,
"groundStationIds": ["1", "2"],
"minElevationDeg": 10
}Search by TLE:
{
"satelliteId": "sat_123",
"tle": {
"name": "SATELLITE NAME",
"line1": "1 ...",
"line2": "2 ..."
},
"days": 7,
"groundStationIds": ["1"],
"minElevationDeg": 10
}Request fields:
| Field | Required | Description |
|---|---|---|
satelliteId | Yes | Stable DSN satellite identity returned by satellite search/list endpoints. |
tle | No | Optional raw TLE override for the selected satellite. |
days | No | Number of days to search. Defaults to 7, maximum 14. |
start | No | Search start timestamp. Defaults to current time. |
end | No | Search end timestamp. Capped to start + 14 days. |
groundStationIds | No | Ground stations to include. If omitted, all configured stations with coordinates are used. |
minElevationDeg | No | Minimum pass elevation in degrees. Defaults to 10. |
stepSeconds | No | Calculation step size. Allowed range is 15-300 seconds. Defaults to 60. |
When tle is supplied, the response includes tleUpdate with refreshed, unchanged, or stale. Malformed TLEs are rejected with 400 before pass calculation.
Response:
{
"satellite": {
"satelliteId": "sat_123",
"noradId": 25544,
"name": "ISS",
"tle": {
"name": "ISS",
"line1": "1 ...",
"line2": "2 ..."
}
},
"groundStations": [
{
"id": "1",
"description": "Ground Station",
"latitude": 42.0,
"longitude": 23.0,
"altitude": 500
}
],
"passes": [
{
"groundStationId": "1",
"rise_time": "2026-05-06T10:00:00.000Z",
"set_time": "2026-05-06T10:10:00.000Z",
"aos": "2026-05-06T10:00:00.000Z",
"los": "2026-05-06T10:10:00.000Z",
"aos_azimuth": 183.1,
"los_azimuth": 31.8,
"max_elevation": 42.3,
"max_elevation_time": "2026-05-06T10:05:00.000Z",
"slant_range_km": 1250.4,
"polar_track": [
{
"azimuth": 183.1,
"elevation": 0,
"time": "2026-05-06T10:00:00.000Z"
}
]
}
],
"total": 1,
"generated_at": "2026-05-05T12:00:00.000Z"
}Bookings
Bookings reserve one or more pass windows for a satellite and mission type.
Create Booking
POST /orbapi/bookings
Authorization: Bearer dsn_live_...Purpose: Reserve one or more ground-station pass windows for a satellite mission.
Required scope:
bookings:createRequest:
{
"satelliteId": "sat_123",
"type": "data",
"direction": "rx",
"clientRequestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"slots": [
{
"start": "2026-05-06T10:00:00Z",
"end": "2026-05-06T10:10:00Z",
"gs_id": "1"
}
],
"uplinkFile": {
"key": "clients/client-storage-slug/uplink/42-uplink-command.bin",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
}Request fields:
| Field | Required | Description |
|---|---|---|
satelliteId | Yes | Stable DSN satellite identity returned by satellite search/list endpoints. |
type | Yes | Mission type: data or test. Legacy lora writes are accepted temporarily and normalized to data; historical lora rows remain readable. |
direction | No | rx or bidirectional. Existing defaulting remains available for legacy clients. |
slots | Yes | Requested reservation windows. |
slots[].start | Yes | Slot start timestamp. |
slots[].end | Yes | Slot end timestamp. |
slots[].gs_id | Yes | Ground station ID. |
clientRequestId | No | Idempotency key (up to 128 characters, e.g. a UUID). Retrying a request with the same clientRequestId returns the original booking with "idempotentReplay": true instead of creating a duplicate. |
uplinkPayloadId | No | ID of a pre-uploaded uplink payload (see Uplink Payloads). The payload must belong to the caller and be in status READY; otherwise the booking is rejected with 400. |
uplinkFile | No | Optional uplink object metadata to attach during booking creation. |
uplinkFile.key | Yes, if uplinkFile is present | Object key in the DSN S3 bucket. |
uplinkFile.sha256 | No | Optional SHA-256 checksum as 64 hexadecimal characters. |
rfPlanSelections | No | Additive per-station choices copied from the RF-plan preview. Omit this field to retain deterministic server defaults. If present, it must cover every requested station. |
rfPlanSelections[].stationId | Yes, if rfPlanSelections is present | A station ID from the requested slots. |
rfPlanSelections[].selectedRxGroupId | Yes, if present | Exactly one stable RX-group ID returned by the preview for this station. |
rfPlanSelections[].selectedSatelliteTransmitterUuids | Yes, if present | One or more unique candidate UUIDs from the selected RX group. |
rfPlanSelections[].selectedStationReceiverUuid | Yes, if present | One compatible station receiver UUID, or null when station receive capability is not catalogued. |
rfPlanSelections[].selectedTxPairId | Yes, if present | One compatible TX-pair ID for a real bidirectional reservation; otherwise null. |
Every selected station must be ONLINE at the authoritative booking write, for every reservation profile and every future slot date. OFFLINE and MAINTENANCE stations, their predicted slots, and existing activities remain visible. A blocked write returns 409 with code: "STATION_NOT_ONLINE". Read-only operations and cancellation remain available.
HTTP/1.1 409 Conflict
Content-Type: application/json{
"error": "Reservation requires every selected ground station to be ONLINE; GS-3886 is MAINTENANCE",
"code": "STATION_NOT_ONLINE"
}Parked zenith RX booking
parked_zenith_rx is an additive execution profile. Requests that omit profile retain the existing tracked-mission behavior and field rules. A parked booking uses the same endpoint:
{
"satelliteId": "schedule-satellite-id",
"type": "data",
"direction": "rx",
"profile": "parked_zenith_rx",
"passPaddingMinutes": 5,
"captureHalfSpanHz": 50000,
"transmitterUuid": "global-catalogue-transmitter-uuid",
"transmitterUuids": [
"global-catalogue-transmitter-uuid",
"modem-equivalent-framing-candidate-uuid"
],
"slots": [
{
"gs_id": "3886",
"aos": "2026-07-20T10:00:00.000Z",
"los": "2026-07-20T10:09:00.000Z"
}
]
}For this profile, type must be data, direction must be rx, and passPaddingMinutes is a required whole number from 0 through 60. captureHalfSpanHz is optional and defaults to 50000; every selected station must advertise support for the requested value. The operations UI explicitly starts new reservations at 25000 (+/-25 kHz), while direct API requests that omit the field retain the established 50000 server default. The padded capture start must leave at least 120 seconds for station setup relative to server validation time; otherwise the request is rejected and the operator must reduce padding or select a later pass. transmitterUuid is required and remains the primary compatibility field. The optional additive transmitterUuids array may contain 1 through 16 unique catalogue UUIDs, must include the primary, and is normalized primary-first. Every candidate must share center frequency, modulation, baud, occupied bandwidth, inversion, and differential settings; framing and FEC may differ. Each transceiver is resolved from the global catalogue, so its owner satellite may differ from satelliteId. Omitting transmitterUuids retains the singular request behavior. The slots contain the unpadded source-pass aos and los; the backend validates the pass and derives capture start/end. Uplink fields, browser-supplied RF tuples, slots[].start/slots[].end, and rfPlanSelections are rejected for this profile.
Booking reads add profile, passPaddingMinutes, captureHalfSpanHz, selectedTransmitterUuid, rfSnapshot, and nullable slots[].sourceAos/sourceLos. New rows also add selectedTransmitterUuids and rfCandidateSnapshots; the singular values remain the primary candidate for older clients. Existing fields and legacy response meanings are unchanged. The RF snapshot separates centerFrequencyHz and occupiedBandwidthHz from the reservation capture span.
Preview the current bounded compatibility plan before submitting explicit choices:
POST /orbapi/bookings/rf-plan-preview
Authorization: Bearer dsn_live_...
Content-Type: application/jsonThe preview accepts satelliteId, type, direction, and slots in the same form as booking creation. It performs no reservation write and returns a station-specific schema-v1 plan with stable rxGroups[].id and txPairs[].id values, satellite candidates, and matching stationReceiverUuids. Submit the selected IDs in the optional rfPlanSelections array:
{
"rfPlanSelections": [
{
"stationId": "1",
"selectedRxGroupId": "rxg-0123456789abcdef01234567",
"selectedSatelliteTransmitterUuids": ["satellite-transmitter-uuid"],
"selectedStationReceiverUuid": "station-receiver-uuid",
"selectedTxPairId": null
}
]
}The backend recomputes the plan during creation and rejects stale, unknown, duplicate, cross-group, or incompatible selections with 409. This contract is backward compatible: rfPlanSelections is optional, existing request fields are unchanged, old stored plans remain readable, and response fields added inside rfPlan are optional. A legacy client that omits the field receives the previous deterministic automatic selection.
Each preview candidate includes normalized framing. An explicit transceiver framing remains authoritative. If it is missing and the catalog mode, name, or description contains an unambiguous GS-supported protocol name or qualified coding variant, the backend supplies the canonical value in preview, the stored plan, and the station command. Bare AX100, AO-40, CCSDS, and SMOG-P remain unspecified because the backend cannot safely choose their coding variant.
A test mission is a simulated bidirectional pass and REQUIRES a command file: either uplinkPayloadId or uplinkFile must be supplied, otherwise the request is rejected with 400.
Response:
{
"status": "success",
"bookingId": 42,
"missionId": 42,
"satelliteId": "sat_123",
"noradId": "25544",
"uplinkFile": {
"id": 1,
"missionId": 42,
"direction": "uplink",
"key": "clients/client-storage-slug/uplink/42-uplink-command.bin",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"status": "pending",
"createdAt": "2026-05-05T12:00:00.000Z",
"updatedAt": "2026-05-05T12:00:00.000Z"
}
}If no uplink file is supplied, uplinkFile is null.
List Bookings
GET /orbapi/bookings
Authorization: Bearer dsn_live_...Purpose: Return the caller's current and historical bookings, including reserved slots.
Optional cursor pagination (additive - omitting limit returns the full list):
| Query parameter | Description |
|---|---|
limit | Maximum number of bookings to return (1-200). Enables pagination. |
cursor | The nextCursor value from the previous page. Bookings with an ID greater than the cursor are returned. |
Example:
GET /orbapi/bookings?limit=50&cursor=42The response includes a nextCursor field: the booking ID to pass as cursor for the next page, or null when there are no further pages (always null when limit is omitted).
Response:
{
"bookings": [
{
"id": 42,
"bookingId": 42,
"missionId": 42,
"ownerId": "client_123",
"ownerType": "api_token",
"satelliteId": "sat_123",
"noradId": "25544",
"type": "data",
"status": "Pending",
"requestedAt": "2026-05-05T12:00:00.000Z",
"slots": [
{
"id": 100,
"missionId": 42,
"gsId": "1",
"start": "2026-05-06T10:00:00.000Z",
"end": "2026-05-06T10:10:00.000Z",
"status": "Pending"
}
]
}
],
"nextCursor": null
}Affected Reservations When a Station Is Unavailable
GET /orbapi/bookings/station-impacts
Authorization: Bearer dsn_live_...Purpose: return the caller's non-terminal active or future reservations whose station is currently OFFLINE or MAINTENANCE. The response groups reservations under impacts[] by stationId and includes operationalStatus, optional statusDetail, and the affected slot IDs and UTC bounds. This advisory read never mutates or hides the reservations. The operations UI checks it on login and periodically while the user remains signed in.
{
"generatedAt": "2026-07-24T10:00:00.000Z",
"impacts": [
{
"stationId": "3886",
"operationalStatus": "MAINTENANCE",
"statusDetail": "kill switch latched",
"reservations": [
{
"bookingId": 71,
"missionId": 71,
"satelliteId": "sat-iss",
"noradId": "25544",
"status": "Pending",
"slots": [
{
"slotId": 710,
"start": "2026-07-24T09:55:00.000Z",
"end": "2026-07-24T10:10:00.000Z",
"status": "InProgress"
}
]
}
]
}
]
}When no active or future reservation is affected, impacts is empty:
{
"generatedAt": "2026-07-24T10:00:00.000Z",
"impacts": []
}Cancel Booking
DELETE /orbapi/bookings/:bookingId
Authorization: Bearer dsn_live_...Purpose: Cancel a booking. A booking can be cancelled up until its pass starts - a booking that has already been dispatched to the station but whose pass is still in the future is freely cancellable (the backend relays a cancel to the station). Once a pass is in progress or has finished, cancellation returns 409 CONFLICT. A booking that does not exist (or that the caller is not permitted to manage) returns 404.
Required scope:
bookings:cancelResponse:
{
"status": "success",
"message": "Booking cancelled successfully",
"bookingId": 42,
"missionId": 42,
"satelliteId": "sat_123",
"noradId": "25544"
}> DELETE /orbapi/mission (with { "missionId": 42 } in the body) is an equivalent alias - it runs > the identical handler and returns the same result.
Satellite Ownership (authorization)
Satellites can be owned by specific customers. Ownership enforcement is always enabled: only an owner of a satellite (or an operator/admin) may:
- update its stored TLE (by supplying a
tleonPOST /orbapi/passes/search), and - create, cancel, and manage bookings for it.
Key points:
- Multiple owners per satellite are supported - co-owners share full control and may cancel or
manage any booking on that satellite, regardless of which owner created it.
- Ownership grants are keyed by
satelliteId + ownerId. - Protected satellites: a satellite with explicit owners is owner/admin-only for booking management and TLE changes. A satellite with no owner grant and no
ownerIdis treated as a generic public catalog satellite and can be reserved by authenticated clients. - A non-owner who calls a gated operation receives
403(booking create / TLE update) or404
(cancel / manage of a specific booking). Read-only pass search (without a tle) and the public satellite catalog remain open.
Mission Profiles
Mission profiles store arbitrary JSON configuration for a satellite. Use satelliteId for profile APIs.
Create Mission Profile
POST /orbapi/satellites/:satelliteId/profiles
Authorization: Bearer dsn_live_...Purpose: Store a caller-owned mission configuration profile for a satellite.
Required scope:
mission-profile:createRequest:
{
"name": "Primary data profile",
"profile": {
"radio": {
"frequencyHz": 437000000,
"modulation": "GMSK",
"baud": 9600
}
}
}The profile wrapper is optional. If omitted, the full request body is stored as profile JSON.
Response:
{
"status": "success",
"profile": {
"id": 12,
"satelliteId": "sat_123",
"satelliteNoradId": 25544,
"name": "Primary data profile",
"profile": {
"radio": {
"frequencyHz": 437000000,
"modulation": "GMSK",
"baud": 9600
}
},
"createdBy": "client_123",
"createdByType": "api_client",
"createdAt": "2026-05-05T12:00:00.000Z",
"updatedAt": "2026-05-05T12:00:00.000Z"
}
}List Mission Profiles For Satellite
GET /orbapi/satellites/:satelliteId/profiles
Authorization: Bearer dsn_live_...Purpose: List mission profiles for the requested satellite that are visible to the caller.
Response:
{
"satelliteId": "sat_123",
"noradId": 25544,
"profiles": [
{
"id": 12,
"satelliteId": "sat_123",
"satelliteNoradId": 25544,
"name": "Primary data profile",
"profile": {},
"createdAt": "2026-05-05T12:00:00.000Z",
"updatedAt": "2026-05-05T12:00:00.000Z"
}
]
}Get Mission Profile
GET /orbapi/satellites/profiles/:profileId
Authorization: Bearer dsn_live_...Purpose: Fetch one mission profile by its profile ID.
Response:
{
"profile": {
"id": 12,
"satelliteId": "sat_123",
"satelliteNoradId": 25544,
"name": "Primary data profile",
"profile": {}
}
}Delete Mission Profile
DELETE /orbapi/satellites/profiles/:profileId
Authorization: Bearer dsn_live_...Purpose: Delete a mission profile owned by the caller, or any profile when using a service token.
Response:
{
"status": "success",
"message": "Mission profile deleted successfully",
"profileId": 12
}Pass Products (Waterfall & Audio)
After a receive pass, the station uploads up to two optional post-pass artifacts per slot: a waterfall spectrogram (waterfall, PNG) and diagnostic audio (audio, Ogg Vorbis). They are stored on the booking and served back to the booking owner.
List the artifacts recorded for a booking:
GET /orbapi/bookings/:bookingId/artifacts
Authorization: Bearer dsn_live_...Response (metadata only; waterfall/audio are null until the station uploads them):
{
"bookingId": 42,
"missionId": 42,
"slots": [
{
"slotId": 7,
"start": "2026-05-06T10:00:00.000Z",
"end": "2026-05-06T10:10:00.000Z",
"waterfall": {
"id": 100,
"type": "waterfall",
"filename": "waterfall.png",
"contentType": "image/png",
"sizeBytes": 1366234,
"sha256": "…",
"uploadedByStationId": "3886",
"createdAt": "2026-05-06T10:12:03.000Z",
"updatedAt": "2026-05-06T10:12:03.000Z"
},
"audio": null
}
]
}Download one artifact's bytes:
GET /orbapi/bookings/:bookingId/slots/:slotId/artifacts/:type
Authorization: Bearer dsn_live_...:type is waterfall or audio. The response streams the raw file with its Content-Type (image/png / audio/ogg), Content-Length, an ETag (the SHA-256), and Content-Disposition (inline for the waterfall, attachment for audio); append ?download=1 to force an attachment. A 404 means that slot has no artifact of that type yet.
Uplink Payloads
Uplink payloads are pre-uploaded, server-verified command files. Unlike raw uplink file metadata, the payload flow gives the client a presigned upload URL and the backend verifies the object (size and SHA-256) before it can be attached to a booking:
1. POST /orbapi/uplink-payloads - create the payload record and receive a presigned uploadUrl. 2. PUT the file content to uploadUrl (direct to object storage, not through the API). 3. Poll GET /orbapi/uplink-payloads/:id until the status settles to READY (or REJECTED). 4. Create a booking with uplinkPayloadId - only READY payloads can be attached.
Files are limited to 67,108,864 bytes (64 MB); larger objects are REJECTED during verification.
Create Uplink Payload
POST /orbapi/uplink-payloads
Authorization: Bearer dsn_live_...Purpose: Register an uplink command file and obtain a presigned upload URL.
Required scope:
payloads:writeRequest:
{
"satelliteId": "sat_123",
"filename": "command-sequence.bin",
"contentType": "application/octet-stream",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}Request fields:
| Field | Required | Description |
|---|---|---|
satelliteId | Yes | Stable DSN identity of the satellite for which the command file is intended. |
filename | Yes | Original filename (max 255 characters). Sanitized into the object key. |
contentType | No | MIME type. Defaults to application/octet-stream. |
sha256 | No | Expected SHA-256 checksum as 64 hexadecimal characters. If provided, verification rejects the upload on mismatch. |
Response (201):
{
"uplinkPayloadId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"uploadUrl": "https://...",
"uploadUrlExpiresIn": 900,
"status": "AWAITING_UPLOAD",
"satelliteId": "sat_123",
"noradId": "25544",
"filename": "command-sequence.bin",
"createdAt": "2026-05-05T12:00:00.000Z"
}The uploadUrl is valid for 15 minutes. Upload the file with an HTTP PUT using the same Content-Type supplied at creation.
Get Uplink Payload Status
GET /orbapi/uplink-payloads/:id
Authorization: Bearer dsn_live_...Purpose: Fetch the payload status. Verification is lazy - polling this endpoint while the payload is AWAITING_UPLOAD triggers the server-side check once the object is present, settling the status to READY or REJECTED.
Only the owner of the payload can read it; other callers receive 404.
Response:
{
"uplinkPayloadId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "READY",
"filename": "command-sequence.bin",
"contentType": "application/octet-stream",
"sizeBytes": 4096,
"sha256Expected": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"sha256Actual": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"rejectReason": null,
"forwardError": null,
"satelliteId": "sat_123",
"noradId": "25544",
"createdAt": "2026-05-05T12:00:00.000Z",
"updatedAt": "2026-05-05T12:05:00.000Z"
}Payload status values:
| Status | Meaning |
|---|---|
AWAITING_UPLOAD | Record created; the object has not been uploaded or verified yet. |
READY | Object verified (size and checksum); the payload can be attached to a booking. |
REJECTED | Verification failed (rejectReason explains: size over 64 MB, or SHA-256 mismatch). |
FORWARDED | The payload was delivered to the ground station by the mission workflow. |
FORWARD_FAILED | Station transfer failed (forwardError explains). |
Uplink File Metadata
The API does not accept file uploads directly. Uplink payloads are delivered to the DSN S3 bucket by the client-side transfer process. The API records the S3 object key and marks the file as pending. The client chooses the uplink object name; the backend only rejects unsafe keys such as empty keys, leading /, .., or keys over 1024 characters.
Attach Uplink File After Booking
POST /orbapi/bookings/:bookingId/uplink-files
Authorization: Bearer dsn_live_...Purpose: Attach uplink S3 object metadata to an existing booking.
Required scope:
bookings:createRequest:
{
"key": "clients/client-storage-slug/uplink/42-uplink-command.bin",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}Response:
{
"status": "success",
"bookingId": 42,
"missionId": 42,
"uplinkFile": {
"id": 1,
"missionId": 42,
"direction": "uplink",
"key": "clients/client-storage-slug/uplink/42-uplink-command.bin",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"status": "pending",
"createdAt": "2026-05-05T12:00:00.000Z",
"updatedAt": "2026-05-05T12:00:00.000Z"
}
}File status values:
| Status | Meaning |
|---|---|
pending | Metadata has been accepted; object verification has not completed. |
available | Object is available for ground-station use. |
consumed | Object has been used by the ground-station workflow. |
failed | Object verification or use failed. |
delivered | Downlink object has been delivered to the client bucket. |
List Uplink Files
GET /orbapi/bookings/:bookingId/uplink-files
Authorization: Bearer dsn_live_...Purpose: List uplink file metadata attached to a booking.
Response:
{
"bookingId": 42,
"missionId": 42,
"files": [
{
"id": 1,
"missionId": 42,
"direction": "uplink",
"key": "clients/client-storage-slug/uplink/42-uplink-command.bin",
"sha256": null,
"status": "pending",
"createdAt": "2026-05-05T12:00:00.000Z",
"updatedAt": "2026-05-05T12:00:00.000Z"
}
]
}Downlink File Metadata
Downlink file contents are delivered to the client S3 bucket by the DSN delivery process. The API is used for status and metadata only.
List Downlink Files
GET /orbapi/bookings/:bookingId/downlink-files
Authorization: Bearer dsn_live_...Purpose: List downlink file metadata produced for a booking.
Response:
{
"bookingId": 42,
"missionId": 42,
"files": [
{
"id": 2,
"missionId": 42,
"direction": "downlink",
"key": "clients/client-storage-slug/downlink/42-downlink-20260604T151102Z-a1b2c3d4-data.bin",
"sha256": null,
"status": "delivered",
"createdAt": "2026-05-06T10:20:00.000Z",
"updatedAt": "2026-05-06T10:21:00.000Z"
}
]
}Reference Data
Station Agent API
Station agents use station-scoped bearer tokens. These endpoints reject requests when the token does not match the :stationId path parameter.
Report Station Capabilities
PUT /orbapi/stations/:stationId/capabilities
Authorization: Bearer dsn_station_<stationId>_<secret>
Content-Type: application/json{
"schemaVersion": 1,
"softwareVersion": "0.0.1",
"parkedZenithRx": {
"enabled": true,
"captureHalfSpanHz": { "min": 25000, "max": 200000, "step": 25000 },
"products": ["waterfall", "audio"]
},
"rfChains": [
{
"chainId": "uhf-rx",
"direction": "rx",
"frequencyRanges": [{ "lowerHz": 430000000, "upperHz": 440000000 }]
}
],
"supportedFramings": ["AX.25", "EnduroSat"],
"maxPassDurationSeconds": 5400
}maxPassDurationSeconds is an optional positive integer: the longest single pass/capture the station will accept, in seconds. When omitted it defaults to 5400 (90 minutes). A booking whose scheduled pass window (slot start to end) exceeds the station's value is rejected at creation with 422 and code PASS_DURATION_EXCEEDED.
supportedFramings is an optional, bounded list of framing labels the reporting station can actually construct. The current GS reports its installed decoder-backed native registry and adds the installed gr-satellites constructor vocabulary only when its service environment contains exactly GS_GRSAT_LIVE=1. Omission means capability unknown for rolling compatibility; an empty list means the station explicitly reports no supported framing.
The endpoint requires station:capabilities:write. For backward compatibility, an existing token with station:telemetry:write is also accepted. Reports are bounded to 64 chains and 16 frequency ranges per chain. The backend stores the newest valid report in additive nullable station fields. An explicit enabled: false or empty RF-chain list is authoritative; omission of a report preserves legacy catalogue behavior. Device elements, local paths, credentials, and power calibration are not part of this contract.
Poll Commands
GET /orbapi/stations/:stationId/commands
Authorization: Bearer dsn_station_<stationId>_<secret>Example:
GET /orbapi/stations/1234/commands
Authorization: Bearer dsn_station_1234_<secret>Result:
{
"stationId": "1234",
"commands": [
{
"id": "cmd_42_7",
"type": "MISSION_SLOT",
"commandStatus": "acknowledged",
"direction": "rx_tx",
"rfLink": {
"satelliteTransmitterUuid": "sat-transmitter-uuid",
"stationTransmitterUuid": "ground-station-transmitter-uuid",
"downlinkHz": 437000000,
"uplinkHz": 145900000,
"mode": "LoRa",
"modeId": 89,
"uplinkMode": "LoRa",
"baud": 9600,
"invert": false,
"bandwidthHz": 125000,
"txPowerMaxDbm": 20,
"eirpMaxDbm": 30
},
"bookingId": 42,
"missionId": 42,
"slotId": 7,
"groundStationId": "1234",
"uplinkFiles": [
{
"fileId": 12,
"key": "clients/client-storage-slug/uplink/42-uplink-command.bin",
"downloadUrl": "https://...",
"transferStatus": "ready",
"transferError": null
}
],
"downlinkUpload": {
"fileId": 13,
"key": "clients/client-storage-slug/downlink/42-downlink-20260604T151102Z-a1b2c3d4-data.bin",
"uploadUrl": "https://...",
"transferStatus": "ready",
"transferError": null,
"refreshUrl": "/orbapi/stations/1234/bookings/42/downlink-files/13/upload-url"
}
}
]
}direction is rx for receive-only slots and rx_tx when uplinkFiles is non-empty. Commands are persisted in the database; polling marks a queued command acknowledged, and terminal statuses stop redelivery.
Every station-authenticated request may include:
X-Station-Status: ONLINE
X-Station-Status-Detail:The running agent sends ONLINE during normal operation and MAINTENANCE while a safety latch is active. The optional detail is a bounded operator-facing reason. Older agents may omit both headers. Every authenticated station call refreshes the heartbeat, including command polling, status, telemetry, capability, URL-refresh, and artifact calls. OFFLINE is derived by the backend after 60 seconds without a station call.
When the catalogue supplies a signed downlink drift, rfLink.downlinkHz is already the authoritative corrected tune frequency. Optional nominalDownlinkHz, downlinkDriftHz, and frequencyCorrectionSource fields provide the audit trail, and downlinkHz = nominalDownlinkHz + downlinkDriftHz. Stations must validate that equation and must not add the drift again. Older commands omit the additive fields and behave unchanged.
For parked_zenith_rx, the additive command fields include missionProfile: "parked_zenith_rx", dopplerCompensation: false, and rfLink.captureBandwidthHz (the full width, twice the booking half-span). The command uses only the persisted RF snapshot, has direction: "rx", and has no generic downlinkUpload grant. The station retains IQ locally and uploads only waterfall PNG and audio OGG through the authenticated artifact route. Older commands and stations keep the existing shape when missionProfile is absent.
The downlink upload key is generated from the booking owner's configured storage slug. If no owner mapping exists yet, the backend falls back to clients/generic/downlink/... so command creation is not blocked.
downloadUrl and downlinkUpload.uploadUrl are nullable. If presigned URL generation is unavailable when the command is queued, the command is still delivered with transferStatus: "pending_presign" and the station should retry the downlink upload URL through downlinkUpload.refreshUrl.
Report Command Status
POST /orbapi/stations/:stationId/commands/:commandId/status
Authorization: Bearer dsn_station_<stationId>_<secret>
Content-Type: application/json{
"status": "completed",
"bookingId": 42,
"slotId": 7,
"completedAt": "2026-06-03T20:18:10Z",
"message": "Pass completed"
}Accepted command statuses:
| Status | Meaning |
|---|---|
acknowledged | Station has received the command. |
in_progress | Station has started executing the pass command. |
completed | Command finished successfully; it will not be delivered again. |
failed | Command failed during execution; it will not be delivered again. |
rejected | Station refused the command before execution; it will not be delivered again. |
cancelled | Command was cancelled; it will not be delivered again. |
When a completed or failed report moves a slot to a terminal state, the parent booking status is recomputed: once every non-cancelled slot of the booking is terminal, the booking rolls up to Completed (at least one pass completed) or Failed. A booking with a still-pending or in-flight pass stays Pending; Cancelled is set only by cancelling the booking.
Refresh Downlink Upload URL
POST /orbapi/stations/:stationId/bookings/:bookingId/downlink-files/:fileId/upload-url
Authorization: Bearer dsn_station_<stationId>_<secret>
Content-Type: application/json{ "expires": 900 }Report Station Telemetry
POST /orbapi/stations/:stationId/telemetry
Authorization: Bearer dsn_station_<stationId>_<secret>
Content-Type: application/json{
"bookingId": 42,
"records": [
{
"timestamp": "2026-06-03T20:12:00Z",
"data": {
"snr": 12.4,
"rssi": -91,
"frequency": 437500000
}
}
]
}Limits: maximum 100 telemetry records per request, and each data object must serialize to 16 KB or less. The recommended station reporting rate is up to 1 request per second unless agreed otherwise.
Satellites
GET /orbapi/satellitesPurpose: List satellites available in the catalog.
Example:
GET /orbapi/satellites?start=0&limit=20Result:
{
"data": [
{
"satelliteId": "sat_123",
"norad_cat_id": 25544,
"name": "ISS",
"names": "ISS (ZARYA)",
"status": "alive"
}
],
"total": 1
}GET /orbapi/satellites/norad/:noradIdPurpose: List every satellite matching a NORAD catalog ID. This route never selects one satellite.
For an unambiguous lookup by stable identity, use GET /orbapi/satellites/:satelliteId.
Example:
GET /orbapi/satellites/norad/25544Result:
{
"data": [
{
"satelliteId": "sat_123",
"norad_cat_id": 25544,
"name": "ISS",
"names": "ISS (ZARYA)",
"status": "alive"
}
],
"total": 1
}GET /orbapi/satellites/name/:namePurpose: Search satellites by name.
Example:
GET /orbapi/satellites/name/ISSResult:
{
"data": [
{
"satelliteId": "sat_123",
"norad_cat_id": 25544,
"name": "ISS",
"names": "ISS (ZARYA)",
"status": "alive"
}
],
"total": 1
}Assets Management
GET /orbapi/catalog-management
Authorization: Bearer dsn_live_...Required scope: catalog:write.
Lists the calling token organization's editable pinned manual satellites, TLEs, and transceivers. The organization is taken from the token; a caller cannot select another organization with query or body fields.
The canonical TLE update uses the stable satellite ID:
PATCH /orbapi/tles/sat_1887805
Authorization: Bearer dsn_live_...
Content-Type: application/json
{
"line1": "1 88246U 26194.85245136 +.00000606 +00000+0 +67191-4 0 00185",
"line2": "2 88246 97.7567 95.1926 0004040 106.5039 253.6623 14.90796340000971"
}The older PATCH /orbapi/catalog-management/tle/:databaseId form remains available for compatibility with the web catalog editor.
The supported kinds and identifiers are:
| Kind | Identifier | Editable fields |
|---|---|---|
satellite | Numeric database id returned by the catalog list | name, names, image |
tle | Stable satelliteId with PATCH /orbapi/tles/:satelliteId; numeric database id on the compatibility catalog route | name, line1, line2 |
transmitter | uuid returned by the catalog list | description, mode, uplinkMode, service, status, alive, invert, baud, bandwidthHz, uplinkLow, uplinkHigh, downlinkLow, downlinkHigh |
For example, edit a satellite or transceiver with PATCH /orbapi/catalog-management/satellite/:id or PATCH /orbapi/catalog-management/transmitter/:uuid.
TLEs
GET /orbapi/tlesPurpose: List current TLE records.
Example:
GET /orbapi/tles?start=0&limit=20Result:
{
"data": [
{
"id": 25544,
"name": "ISS",
"tle0": "ISS (ZARYA)",
"tle1": "1 ...",
"tle2": "2 ..."
}
],
"total": 1
}GET /orbapi/tles/norad/:noradIdPurpose: List TLE records for every satellite matching a NORAD catalog ID.
For operational use, fetch it by stable identity with GET /orbapi/tles/:satelliteId.
Example:
GET /orbapi/tles/norad/25544Result:
{
"data": [
{
"id": 25544,
"name": "ISS",
"tle0": "ISS (ZARYA)",
"tle1": "1 ...",
"tle2": "2 ..."
}
],
"total": 1
}GET /orbapi/tles/name/:namePurpose: Search TLE records by satellite name.
Example:
GET /orbapi/tles/name/ISSResult:
{
"data": [
{
"id": 25544,
"name": "ISS",
"tle0": "ISS (ZARYA)",
"tle1": "1 ...",
"tle2": "2 ..."
}
],
"total": 1
}Ground Stations
GET /orbapi/gsPurpose: List ground stations in the network.
Example:
GET /orbapi/gs?start=0&limit=20Result:
{
"data": [
{
"id": "3886",
"description": "DSN Stob W1AW 2m dish and 70GHz V-band",
"latitude": 42.09963,
"longitude": 23.106661,
"altitude": 472,
"parkedZenithRxSupported": true,
"captureHalfSpanHzMin": 25000,
"captureHalfSpanHzMax": 200000,
"captureHalfSpanHzStep": 25000,
"parkedZenithRxCapabilitySource": "station-report",
"capabilitiesReportedAt": "2026-07-20T18:00:00.000Z",
"capabilitiesSoftwareVersion": "0.0.1",
"supportedFramings": ["AX.25", "EnduroSat"],
"rfChains": [
{
"chainId": "uhf-rx",
"direction": "rx",
"frequencyRanges": [{ "lowerHz": 430000000, "upperHz": 440000000 }]
}
]
}
],
"total": 1
}All capability-report fields are additive. parkedZenithRxCapabilitySource identifies station-report, legacy catalog, or none. Missing or malformed capability data returns parkedZenithRxSupported: false with null bounds, so legacy station records remain valid and the booking UI fails closed for this profile. When a valid report exists, RF-plan responses may also include additive stationRxChainIds and stationTxChainIds arrays; existing receiver and pair selection fields are unchanged. Each station RF plan also carries nullable supportedFramings so clients can distinguish supported, unsupported, and not-yet-reported framing capability.
GET /orbapi/gs/id/:idPurpose: Fetch one ground station by ID.
Example:
GET /orbapi/gs/id/1Result:
{
"data": [
{
"id": "1",
"description": "Ground Station",
"latitude": 42.0,
"longitude": 23.0,
"altitude": 500
}
],
"total": 1
}GET /orbapi/gs/id/:id/statusPurpose: Report command-broker heartbeat status for a ground station.
Example:
GET /orbapi/gs/id/1/statusResult:
{
"groundStation": {
"id": "1",
"description": "Ground Station"
},
"host": "1",
"online": true,
"status": "maintenance",
"operationalStatus": "MAINTENANCE",
"statusDetail": "kill switch latched",
"lastSeenAt": "2026-05-05T12:00:00.000Z",
"lastSeenAgeMs": 1200,
"queueDepth": 0
}GET /orbapi/gs/description/:descriptionPurpose: Search ground stations by description.
Example:
GET /orbapi/gs/description/SofiaResult:
{
"data": [
{
"id": "4567",
"description": "DSN Sofia Tech Park LoRa and X-band/V-band reflector",
"latitude": 42.667693,
"longitude": 23.373397,
"altitude": 630
}
],
"total": 1
}Ground-station status is based on authenticated station heartbeats. Every authenticated station call updates lastSeenAt. operationalStatus is exactly ONLINE, OFFLINE, or MAINTENANCE. The existing lowercase status and boolean online remain for compatibility; a maintenance station is still connected. After 60 seconds without a heartbeat, the effective state is always OFFLINE. Status does not filter station details, slots, reservations, telemetry, or products. New reservations and other commands that initiate station work require ONLINE.
{
"groundStation": {
"id": "1",
"description": "Ground Station"
},
"host": "1",
"online": true,
"status": "maintenance",
"operationalStatus": "MAINTENANCE",
"statusDetail": "kill switch latched",
"lastSeenAt": "2026-05-05T12:00:00.000Z",
"lastSeenAgeMs": 1200,
"queueDepth": 0
}Transceivers
The operations UI and prose documentation use transceiver as the human-facing term. Existing REST paths, JSON fields, database models, and code identifiers remain transmitter / transmitters for backward compatibility.
GET /orbapi/transmittersPurpose: List transceiver records. Every returned record includes an additive nullable framing field. Explicit catalogue framing wins; otherwise the backend infers only an unambiguous, qualified protocol label from the mode, name, or description. Mode V APRS, for example, is normalized to AX.25, while bare AFSK remains unspecified. Existing clients may ignore this field.
Example:
GET /orbapi/transmitters?start=0&limit=20Result:
{
"data": [
{
"uuid": "ZzPz4gcsNBPKPKAFPmer7g",
"description": "DSN Stob LoRa(868MHz) band",
"mode": "LoRa",
"mode_id": 89,
"gs_id": "3886",
"framing": null,
"status": "active"
}
],
"total": 1
}GET /orbapi/transmitters/norad/:noradIdPurpose: List transceivers for every satellite matching a NORAD catalog ID.
For operational use, list them by stable identity with GET /orbapi/transmitters/satellite/:satelliteId.
Example:
GET /orbapi/transmitters/norad/25544Result:
{
"data": [
{
"uuid": "sat-transmitter-uuid",
"satelliteId": "sat_123",
"norad_cat_id": 25544,
"description": "Satellite downlink",
"mode": "LoRa",
"mode_id": 89,
"gs_id": null,
"framing": null
}
],
"total": 1
}GET /orbapi/transmitters/uuid/:uuidPurpose: Fetch one transceiver by UUID.
Example:
GET /orbapi/transmitters/uuid/550e8400-e29b-41d4-a716-446655440000Result:
{
"data": [
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"description": "Transmitter",
"mode": "LoRa",
"mode_id": 89,
"gs_id": "1"
}
],
"total": 1
}GET /orbapi/transmitters/mode/:modeIdPurpose: List transceivers that support a mode such as lora or a numeric mode ID.
Example:
GET /orbapi/transmitters/mode/loraResult:
{
"data": [
{
"uuid": "ZzPz4gcsNBPKPKAFPmer7g",
"description": "DSN Stob LoRa(868MHz) band",
"mode": "LoRa",
"mode_id": 89,
"gs_id": "3886"
}
],
"total": 1
}GET /orbapi/transmitters/norad/:noradId/mode/:modeIdPurpose: List transceivers for one satellite filtered by mode.
Example:
GET /orbapi/transmitters/norad/25544/mode/loraResult:
{
"data": [
{
"uuid": "sat-transmitter-uuid",
"satelliteId": "sat_123",
"norad_cat_id": 25544,
"description": "Satellite LoRa downlink",
"mode": "LoRa",
"mode_id": 89
}
],
"total": 1
}GET /orbapi/transmitters/gs/:gsIdPurpose: List transceivers associated with a ground station.
Example:
GET /orbapi/transmitters/gs/1Result:
{
"data": [
{
"uuid": "ground-station-transmitter-uuid",
"description": "Ground station transmitter",
"mode": "LoRa",
"mode_id": 89,
"gs_id": "1"
}
],
"total": 1
}GET /orbapi/transmitters/gs/:gsId/mode/:modeIdPurpose: List ground-station transceivers filtered by mode.
Example:
GET /orbapi/transmitters/gs/1/mode/loraResult:
{
"data": [
{
"uuid": "ground-station-transmitter-uuid",
"description": "Ground station LoRa transmitter",
"mode": "LoRa",
"mode_id": 89,
"gs_id": "1"
}
],
"total": 1
}