Ephem · developer · SDK examples
Talking to the API from your language.
Ephem does not publish a language SDK. The API is small enough (one JSON envelope shape, Bearer auth, RESTful paths) that a hand-written wrapper is thirty lines in any language. Copy the snippet you need, wire it into your own error and retry policy, and ship. If you find yourself writing enough boilerplate to justify a package, publish it yourself under whatever name you like — Ephem does not gatekeep client libraries.
§1 Authentication
Every keyed endpoint accepts either header:
Authorization: Bearer ek_live_XXXXXXXXXXXXXXXXXXXXXXXX
x-api-key: ek_live_XXXXXXXXXXXXXXXXXXXXXXXX
Send one. Both is not an error but is pointless. Keys are one-per-IP-per-day self-serve via POST /v1/keys; enterprise keys come with a signed contract and higher tier limits.
Public endpoints (/v1/tle/{group}, /ref/*, /v1/speedtest/*) accept requests without a key.
§2 Envelope shape
Every JSON response follows one shape. Failures are HTTP 4xx/5xx with a body of { "error": "code", "message": "..." }. Never a 200 with an error inside.
{
"source": "NOAA/SWPC", // upstream provenance
"fetched_at": "2026-08-04T14:00:00Z", // when Ephem pulled it
"valid_until": "2026-08-04T14:15:00Z", // stale after this timestamp
"data": { ... } // payload — endpoint-specific
}
§3 curl
Object elements · /v1/objects/{norad_id}
curl -sSf https://ephem-api.YOUR-SUBDOMAIN.workers.dev/v1/objects/25544 \
-H "Authorization: Bearer $EPHEM_KEY"
-sSf is the trick: silent, show errors, exit non-zero on HTTP failure. Pipe to jq for interactive use.
TLE group · /v1/tle/{group}
curl -sSf https://ephem-api.YOUR-SUBDOMAIN.workers.dev/v1/tle/starlink \
-o starlink.tle
# 3-line-element format, one TLE per object (name, l1, l2).
No key required for TLE groups. Cache aggressively — the upstream cron refreshes every 6 hours.
Shell density · /shell/density/{alt}
curl -sSf "https://ephem-api.YOUR-SUBDOMAIN.workers.dev/shell/density/550" \
-H "Authorization: Bearer $EPHEM_KEY" \
| jq '.data.bins | sort_by(-.density_per_km3) | .[0:5]'
Top 5 densest bins at 550 km, one line of jq.
§4 Python
Standard library only — no requests, no httpx. Ephem's clients are not important enough to earn a dependency.
python 3.10+ · urllib.request
import json, os, urllib.request
API = "https://ephem-api.YOUR-SUBDOMAIN.workers.dev"
KEY = os.environ["EPHEM_KEY"]
def _get(path):
req = urllib.request.Request(f"{API}{path}", headers={
"Authorization": f"Bearer {KEY}",
"User-Agent": "my-app/0.1",
})
with urllib.request.urlopen(req, timeout=10) as r:
return json.load(r)
def object_elements(norad_id: int) -> dict:
env = _get(f"/v1/objects/{norad_id}")
return env["data"]
def shell_density(altitude_km: int) -> dict:
env = _get(f"/shell/density/{altitude_km}")
return env["data"]
if __name__ == "__main__":
iss = object_elements(25544)
print(f"ISS at {iss['epoch']}, i={iss['inclination_deg']:.2f} deg")
Add tenacity-style retry only if your workload needs it. The API's own cache means most retries are answered from the edge inside 50 ms.
§5 JavaScript / TypeScript
Runtime-neutral (Node ≥ 18, Deno, Bun, Cloudflare Workers, modern browsers). Uses fetch; no dependencies.
TypeScript · fetch
const API = "https://ephem-api.YOUR-SUBDOMAIN.workers.dev";
const KEY = process.env.EPHEM_KEY!;
interface Envelope<T> {
source: string;
fetched_at: string;
valid_until: string;
data: T;
}
async function ephem<T>(path: string): Promise<T> {
const res = await fetch(`${API}${path}`, {
headers: {
"authorization": `Bearer ${KEY}`,
"user-agent": "my-app/0.1",
},
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "unknown" }));
throw new Error(`ephem ${res.status}: ${err.error} — ${err.message ?? ""}`);
}
const env = (await res.json()) as Envelope<T>;
return env.data;
}
// Example — fetch the densest bin at 550 km
interface ShellDensity { bins: { latitude_deg:number; longitude_deg:number; density_per_km3:number; objects:number; }[]; }
const shell = await ephem<ShellDensity>("/shell/density/550");
const peak = shell.bins.reduce((a, b) => b.density_per_km3 > a.density_per_km3 ? b : a);
console.log(peak);
Works unchanged in a Cloudflare Worker — replace process.env with the Worker's env binding.
§6 Go
Go 1.21+ · net/http + encoding/json
package ephem
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
const api = "https://ephem-api.YOUR-SUBDOMAIN.workers.dev"
type Envelope[T any] struct {
Source string `json:"source"`
FetchedAt time.Time `json:"fetched_at"`
ValidUntil time.Time `json:"valid_until"`
Data T `json:"data"`
}
type errBody struct {
Error string `json:"error"`
Message string `json:"message"`
}
var client = &http.Client{Timeout: 10 * time.Second}
func Get[T any](ctx context.Context, path string) (T, error) {
var zero T
req, _ := http.NewRequestWithContext(ctx, "GET", api+path, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("EPHEM_KEY"))
req.Header.Set("User-Agent", "my-app/0.1")
res, err := client.Do(req)
if err != nil {
return zero, err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var e errBody
_ = json.NewDecoder(res.Body).Decode(&e)
return zero, fmt.Errorf("ephem %d: %s: %s", res.StatusCode, e.Error, e.Message)
}
var env Envelope[T]
if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
return zero, err
}
return env.Data, nil
}
// Example — request the ISS object.
type Object struct {
NoradID int `json:"norad_id"`
Epoch string `json:"epoch"`
InclinationDeg float64 `json:"inclination_deg"`
}
// obj, err := Get[Object](ctx, "/v1/objects/25544")
Generics land the envelope cleanly. Pre-Go-1.18 users can drop generics and add a wrapper per response type — the transport is the reusable piece.
§7 Retry, rate limits, error codes
The API returns standard HTTP semantics — retries and back-off are the caller's business. That said:
- 429 — you hit the per-tier rate limit. Body includes
retry_after_seconds; honour it. - 502 / 503 — an upstream (NOAA, CelesTrak, LL2) is transiently down. Retry after 30–60 s with jitter.
- 401 — bad or expired key. Do not retry.
- 404 on
/shell/density/{alt}— nightly cron has not populated that shell yet. Retry after next cron slot (03:17 UTC).
Every response includes an x-ephem-request-id header. Include it in support emails; it lets Ephem trace a request across the edge cache and origin logs.
§8 OpenAPI spec
The full machine-readable spec is at https://ephem-api.YOUR-SUBDOMAIN.workers.dev/openapi.json. Feed it to openapi-generator, swagger-codegen, or Prism (for mocking) if you want an auto-generated client.
The spec is served from the same worker as the API, so its version and the API version can never diverge. If a code path exists that is not in the spec, that is a bug — please report it.