import type { Metadata } from 'next'; import Link from 'next/link'; import { ENDPOINT_GROUPS, RATE_LIMITS } from '@/components/meta/endpoints'; import { ATTRIBUTION, DISCLAIMER } from '@/components/meta/legal'; import { Callout, Code, DocLayout, DocSection, Prose } from '@/components/meta/prose'; import { Container, PageHeader } from '@/components/ui/section'; import { CONTACT_EMAIL, SITE_URL, routes } from '@/lib/site'; export const metadata: Metadata = { title: 'API for developers — free JSON access to the orbital catalog', description: 'The SatelliteIndex REST API: satellites, live SGP4 positions, constellations, operators, countries, launches, debris, reentries, events and statistics as JSON. Free during the MVP.', alternates: { canonical: `${SITE_URL}/developers` }, openGraph: { title: 'API | SatelliteIndex', description: 'Free JSON API for everything in Earth orbit — the same API that powers this site.', url: `${SITE_URL}/developers` }, twitter: { card: 'summary', title: 'API | SatelliteIndex', description: 'Free JSON API for everything in Earth orbit.' }, }; const BASE = `${SITE_URL}/api/v1`; const TOC = [ { id: 'basics', label: 'Basics' }, { id: 'envelope', label: 'Response envelope' }, ...ENDPOINT_GROUPS.map((g) => ({ id: `ep-${g.title.toLowerCase().replace(/[^a-z]+/g, '-')}`, label: g.title })), { id: 'positions-format', label: 'Batch positions format' }, { id: 'rate-limits', label: 'Rate limits' }, { id: 'attribution', label: 'Attribution & terms' }, { id: 'roadmap', label: 'Keys and tiers' }, ]; export default function DevelopersPage() { return (

Interactive docs (Swagger) OpenAPI 3 schema Status

Base URL: {BASE}. All endpoints are GET, return application/json encoded in UTF-8, and use UTC ISO-8601 timestamps. Units are kilometres, km/s, kilograms, degrees and minutes. Countries are ISO 3166-1 alpha-2 codes. Numbers aggregated by the database may arrive as strings — parse them as decimals.

Satellites can be addressed by slug (iss-zarya-25544), NORAD catalog number (25544) or COSPAR designator (1998-067A). Internal ids are prefixed ULIDs (sat_…) and are stable; NORAD and COSPAR are source identifiers and are kept as such.

{`curl -s "${BASE}/satellites/25544" | jq '.data | {name, norad_id, status, orbit_class, live}'`}

Single resources return {'{ data, meta }'}; list endpoints add a pagination block. meta.request_id is the id you will find in our server logs if you report a problem; meta.generated_at is the server time of the response (responses may be cached for up to a few minutes).

{`{ "data": [ … ], "pagination": { "page": 1, "page_size": 50, "total": 17026, "pages": 341 }, "meta": { "request_id": "b382117229134a57", "generated_at": "2026-09-11T18:17:53Z" } }`}

Errors use the same envelope with an error object: {'{ "error": { "title", "detail", "status" } }'} and the matching HTTP status (404 unknown entity, 422 invalid parameter, 429 rate limited, 503 upstream/database unavailable). Paginate with page and page_size (1–200; sitemap feeds allow more).

{ENDPOINT_GROUPS.map((g) => (
    {g.endpoints.map((e) => (
  • {e.method} {e.path} {e.bucket && rate bucket: {e.bucket}}

    {e.summary}

    {`curl -s "${BASE}${e.example}"`}
  • ))}
))}

/orbit/positions returns the position of every object in the propagator (tens of thousands) at two instants, t0 and t1 = t0 + step_s, so a client can interpolate smoothly between polls. To keep the payload small the response is a set of parallel arrays rather than one object per satellite: index i of each array describes the same object.

{`{ "t0": "…", "t1": "…", "step_s": 60, "count": N, "total_tracked": N, "fields": ["norad", "cls", "mission", "active", "pos", "vel"], "norad": [25544, …], // NORAD id per object "cls": [0, …], // index into legend.cls (orbit class) "mission": [8, …], // index into legend.mission "active": [1, …], // 1 = ACTIVE status "pos": [lat0, lon0, alt0, lat1, lon1, alt1, …], // 6 numbers per object (deg, deg, km) "vel": [v0, v1, …], // km/s at t0 and t1 "legend": { "cls": ["LEO","MEO","GEO","HEO","OTHER"], "mission": [ … ] } }`}

Positions are derived from the latest element set of each object and inherit its age; check /sources/status for the median element age before relying on them.

Limits are applied per client IP over a sliding 60-second window. Exceeding a bucket returns 429 Too Many Requests with the bucket name in the error detail. Cache responses on your side; most datasets change hourly at most.

{RATE_LIMITS.map((r) => ( ))}
Bucket Requests / min Applies to
{r.bucket} {r.perMinute} {r.applies}

If you republish data from this API you must keep the upstream attributions. The orbital elements and catalog come from CelesTrak, whose terms require credit: “Orbital data courtesy of CelesTrak.” Derived values (orbit class, mission type, constellation membership, activity score, density) should be credited to SatelliteIndex with a link to the methodology. The full source list with licenses is on /sources.

{ATTRIBUTION}

{DISCLAIMER}

Full terms of use.

The API is free during the MVP and requires no key. API keys, higher rate limits and paid tiers for heavy or commercial use will come later; the public endpoints documented here will keep a free tier. If you are building on the API, say hello at {CONTACT_EMAIL} so we can warn you before any breaking change.

); }