Ground segment nominal UTC ----.--.-- --:--:--

Documentation

Everything needed to get a station streaming, a spacecraft registered and a query running. The API is versioned at /v1 and the version in the path is the version you pinned, not the version we happen to run.

REST / JSON Python SDK Terraform provider OpenAPI 3.1
Search covers the API reference, the guides and the changelog.

Quickstart

Three calls and a station configuration. The examples below assume an API token in the environment variable HELION_API_TOKEN.

1. Register the spacecraft

Registering a spacecraft creates its identity and reserves its COSPAR identifier in the registry. The APID map can be added immediately or uploaded later.

curl -X POST https://api.helionhq.link/v1/spacecraft \
  -H "Authorization: Bearer $HELION_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "AURORA-1",
    "cospar_id": "2024-013A",
    "bus": "hexagon-100",
    "orbit": "leo-sso-530km",
    "apid_map": [
      { "apid": 0x41, "name": "housekeeping", "period_s": 1.0 },
      { "apid": 0x42, "name": "payload",     "period_s": 0.5 }
    ]
  }'

A response of 201 returns the spacecraft identifier, which every later call references.

2. Issue a station credential

A station credential is scoped to the spacecraft it may send for and to the ingest endpoint it may reach. It is shown once.

curl -X POST https://api.helionhq.link/v1/station-keys \
  -H "Authorization: Bearer $HELION_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "spacecraft_id": "sc_9f3a71c4",
    "label": "gs-rey-a",
    "endpoints": ["ingest.eu-north.helionhq.link:443"]
  }'

The secret is returned once in the response body and is not recoverable afterwards.

3. Query a parameter

Parameters are addressed by spacecraft, APID and parameter name. The response carries both the spacecraft time and the station receive time, because the difference between them is usually the interesting number.

curl -G https://api.helionhq.link/v1/parameters \
  -H "Authorization: Bearer $HELION_API_TOKEN" \
  --data-urlencode "spacecraft_id=sc_9f3a71c4" \
  --data-urlencode "parameter=battery_bus_voltage" \
  --data-urlencode "from=2026-01-14T00:00:00Z" \
  --data-urlencode "to=2026-01-14T01:00:00Z"

With the Python SDK

The SDK handles pagination, retries with backoff and token refresh. It is published to PyPI as helion-sdk and requires Python 3.9 or newer.

from helion import Client

client = Client(token=os.environ["HELION_API_TOKEN"])

series = client.parameters.query(
    spacecraft_id="sc_9f3a71c4",
    parameter="battery_bus_voltage",
    start="2026-01-14T00:00:00Z",
    end="2026-01-14T01:00:00Z",
    limit=1000,
)

for sample in series:
    print(sample.sc_time, sample.value, sample.unit, sample.quality)

API reference

The reference below is a summary. The complete, machine-readable description is published as OpenAPI 3.1 and can be fetched from the endpoint itself.

OpenAPI schema →
MethodPathDescriptionAuth
GET/v1/spacecraftList spacecraft visible to the tokenToken
POST/v1/spacecraftRegister a spacecraftOwner
GET/v1/spacecraft/{id}Spacecraft detail and registry versionToken
PUT/v1/spacecraft/{id}/apid-mapReplace the APID dictionaryOperator
GET/v1/parametersQuery parameter samplesToken
GET/v1/parameters/{name}Parameter metadata and unitsToken
GET/v1/gapsList telemetry gapsToken
GET/v1/exportsList export jobsToken
POST/v1/exportsCreate an export jobOperator
GET/v1/windowsContact windows for a spacecraftToken
POST/v1/commandsQueue an uplink commandOperator
POST/v1/commands/{id}/approveApprove a critical commandApprover
GET/v1/alertsList alert instances and stateToken
POST/v1/alerts/rulesCreate an alert ruleOperator
GET/v1/auditRead the append-only audit logOwner
GET/v1/station-keysList station credentialsOwner
POST/v1/station-keysIssue a station credentialOwner

Pagination

List endpoints use cursor pagination. Pass the cursor value from the previous response to fetch the next page. Offsets are supported for small result sets but are not recommended: the underlying data set changes while you page through it.

Idempotency

Write endpoints accept an Idempotency-Key header. Replaying a request with the same key returns the original response and does not create a second resource. Keys are scoped per endpoint and retained for 24 hours.

Timestamps

All timestamps are RFC 3339 in UTC with millisecond precision. Ranges are half-open: from is inclusive and to is exclusive, which lets adjacent queries tile without double-counting a boundary sample.

Ingest guide

How the pieces fit together on the station side.

Connecting a station

The station opens an outbound TLS 1.3 connection to the ingest endpoint. Mutual TLS is used on the Fleet plan; on other plans a bearer credential in the Authorization header is sufficient. The stream is a length-prefixed sequence of transport frames in the order the antenna received them.

  • TLS 1.3 with ALPN helion-tm/1
  • Frames sent as a continuous stream, never as a batch file
  • The station may reconnect at any time and resume from its own buffer
  • No inbound connection to the station is ever required

Acknowledgement semantics

The acknowledgement means the frame is durably stored at the ingest edge. It does not mean the frame is queryable. The gap between the two is the ingest latency, measured and published on the status page as a percentile distribution rather than an average.

The APID dictionary

The dictionary maps APIDs to parameter sets and declares the secondary header type for each virtual channel. It is versioned: an update creates a new version, and samples already in the archive keep the scale factors that were in force when they were decoded.

  • APIDs may be declared individually or as a range with a mask
  • Parameter scale and offset are stored with the sample
  • Unmapped APIDs are written to a quarantine bucket, never dropped
  • A dictionary change is diffed and logged like any other operator action

Virtual channels

Virtual channel identifiers are preserved end to end. If your spacecraft separates housekeeping, payload and beacon traffic into three channels, you can query them independently, and the gap ledger reports gaps per channel rather than in aggregate.

Error codes

Errors return a JSON body with a stable application code, a human-readable message and, where relevant, the offending field. The code is what you should branch on; the message may change.

HTTPCodeMeaningWhat to do
400invalid_requestThe request body failed schema validationCheck the field named in details
400invalid_time_rangefrom is not earlier than toReverse the bounds
401unauthenticatedMissing or malformed credentialSet the Authorization header
401token_expiredThe token passed its expiryRefresh the token
403insufficient_scopeThe token lacks the required scopeRequest the scope or use an operator token
403spacecraft_forbiddenThe token has no grant for this spacecraftAsk an owner for a grant
404not_foundNo such resource, or no grant to see itVerify the identifier
409idempotency_conflictThe key was used with a different payloadUse a new key
409registry_version_conflictThe registry changed since the version you readRe-read the version and reapply
413payload_too_largeRequest body above 2 MiBUse the bulk import endpoint
422schema_mismatchFrames do not match the declared APID mapInspect the quarantine bucket
422units_ambiguousThe parameter exists with more than one unitPass the unit explicitly
429rate_limitedToo many requestsRead Retry-After and back off
500internal_errorAn unexpected faultRetry with backoff, then contact support with the request id
502upstream_unavailableA dependency did not respondRetry with backoff
503maintenancePlanned maintenance in progressCheck the maintenance window on the status page
504gateway_timeoutThe request exceeded the 30-second budgetNarrow the query range

Reading an error body

{
  "code": "schema_mismatch",
  "message": "packet length field exceeds frame boundary",
  "request_id": "req_01HQ8ZC4K2M7",
  "details": {
    "spacecraft_id": "sc_9f3a71c4",
    "virtual_channel": 0,
    "apid": 66,
    "frame_length": 1024
  }
}

Quote the request_id in a support request. It is the identifier our logs are indexed by, and it saves a round trip.

Retries

Retry on 429, 502, 503 and 504 with exponential backoff and jitter. Do not retry a 4xx other than 429 — the request will fail again in the same way. The SDK applies a default policy of four attempts over roughly thirty seconds, and honours the Retry-After header when it is present.

Versioning

Breaking changes require a new major version in the path. Additive changes, new response fields and new enum values may appear in a minor version at any time, so parse defensively: ignore fields you do not recognise and treat an unknown enum value as unknown rather than as an error.

Rate limits and quotas

Limits are applied per token, not per account, and are reported on every response.

Endpoint classOperatorConstellationFleetWindow
Parameter queries60 req/min240 req/minNegotiated1 minute
Gap and window reads120 req/min480 req/minNegotiated1 minute
Export creation10 req/h60 req/hNegotiated1 hour
Command submission60 req/min240 req/minNegotiated1 minute
Station key issue5 req/h30 req/hNegotiated1 hour
Ingest stream8.4 Mbit/s8.4 Mbit/sNegotiatedContinuous
Reading the headers. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A client that watches the remaining counter and slows down before it reaches zero will never see a 429.

Something missing from the docs?

We would rather write the missing page than have you guess. Tell us what you were trying to do and what happened instead.