SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
9.8 KB · 173 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { ENDPOINT_GROUPS, RATE_LIMITS } from '@/components/meta/endpoints';4import { ATTRIBUTION, DISCLAIMER } from '@/components/meta/legal';5import { Callout, Code, DocLayout, DocSection, Prose } from '@/components/meta/prose';6import { Container, PageHeader } from '@/components/ui/section';7import { CONTACT_EMAIL, SITE_URL, routes } from '@/lib/site';89export const metadata: Metadata = {10  title: 'API for developers — free JSON access to the orbital catalog',11  description: 'The SatelliteIndex REST API: satellites, live SGP4 positions, constellations, operators, countries, launches, debris, reentries, events and statistics as JSON. Free during the MVP.',12  alternates: { canonical: `${SITE_URL}/developers` },13  openGraph: { title: 'API | SatelliteIndex', description: 'Free JSON API for everything in Earth orbit — the same API that powers this site.', url: `${SITE_URL}/developers` },14  twitter: { card: 'summary', title: 'API | SatelliteIndex', description: 'Free JSON API for everything in Earth orbit.' },15};1617const BASE = `${SITE_URL}/api/v1`;1819const TOC = [20  { id: 'basics', label: 'Basics' },21  { id: 'envelope', label: 'Response envelope' },22  ...ENDPOINT_GROUPS.map((g) => ({ id: `ep-${g.title.toLowerCase().replace(/[^a-z]+/g, '-')}`, label: g.title })),23  { id: 'positions-format', label: 'Batch positions format' },24  { id: 'rate-limits', label: 'Rate limits' },25  { id: 'attribution', label: 'Attribution & terms' },26  { id: 'roadmap', label: 'Keys and tiers' },27];2829export default function DevelopersPage() {30  return (31    <Container>32      <PageHeader eyebrow="Developers" title="The SatelliteIndex API" lede="Every page on this site is rendered from the public JSON API documented here — there is no private dataset behind it. Free, no key required during the MVP, rate-limited per client.">33        <p className="mt-4 flex flex-wrap gap-x-4 gap-y-1 text-sm">34          <a href="/api/v1/docs" className="link">35            Interactive docs (Swagger)36          </a>37          <a href="/api/v1/openapi.json" className="link">38            OpenAPI 3 schema39          </a>40          <Link href={routes.status()} className="link">41            Status42          </Link>43        </p>44      </PageHeader>4546      <DocLayout toc={TOC}>47        <div>48          <DocSection id="basics" title="Basics">49            <Prose>50              <p>51                Base URL: <code>{BASE}</code>. All endpoints are <code>GET</code>, return <code>application/json</code> 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.52              </p>53              <p>54                Satellites can be addressed by slug (<code>iss-zarya-25544</code>), NORAD catalog number (<code>25544</code>) or COSPAR designator (<code>1998-067A</code>). Internal ids are prefixed ULIDs (<code>sat_…</code>) and are stable; NORAD and COSPAR are source identifiers and are kept as such.55              </p>56            </Prose>57            <Code label="curl">{`curl -s "${BASE}/satellites/25544" | jq '.data | {name, norad_id, status, orbit_class, live}'`}</Code>58          </DocSection>5960          <DocSection id="envelope" title="Response envelope and pagination">61            <Prose>62              <p>63                Single resources return <code>{'{ data, meta }'}</code>; list endpoints add a <code>pagination</code> block. <code>meta.request_id</code> is the id you will find in our server logs if you report a problem; <code>meta.generated_at</code> is the server time of the response (responses may be cached for up to a few minutes).64              </p>65            </Prose>66            <Code label="Shape">{`{67  "data": [ … ],68  "pagination": { "page": 1, "page_size": 50, "total": 17026, "pages": 341 },69  "meta": { "request_id": "b382117229134a57", "generated_at": "2026-09-11T18:17:53Z" }70}`}</Code>71            <Prose className="mt-3">72              <p>73                Errors use the same envelope with an <code>error</code> object: <code>{'{ "error": { "title", "detail", "status" } }'}</code> and the matching HTTP status (404 unknown entity, 422 invalid parameter, 429 rate limited, 503 upstream/database unavailable). Paginate with <code>page</code> and <code>page_size</code> (1–200; sitemap feeds allow more).74              </p>75            </Prose>76          </DocSection>7778          {ENDPOINT_GROUPS.map((g) => (79            <DocSection key={g.title} id={`ep-${g.title.toLowerCase().replace(/[^a-z]+/g, '-')}`} title={g.title} eyebrow="Endpoints">80              <ul className="divide-y divide-rule border-y border-rule">81                {g.endpoints.map((e) => (82                  <li key={e.path} className="py-3">83                    <div className="flex flex-wrap items-center gap-2">84                      <span className="mono rounded bg-plane-3 px-1.5 py-0.5 text-[11px] text-accent">{e.method}</span>85                      <code className="mono break-all text-sm text-ink">{e.path}</code>86                      {e.bucket && <span className="text-[11px] text-ink-3">rate bucket: {e.bucket}</span>}87                    </div>88                    <p className="mt-1 text-sm text-ink-2">{e.summary}</p>89                    <Code className="mt-2">{`curl -s "${BASE}${e.example}"`}</Code>90                  </li>91                ))}92              </ul>93            </DocSection>94          ))}9596          <DocSection id="positions-format" title="Batch positions format">97            <Prose>98              <p>99                <code>/orbit/positions</code> returns the position of every object in the propagator (tens of thousands) at two instants, <code>t0</code> and <code>t1 = t0 + step_s</code>, so a client can interpolate smoothly between polls. To keep the payload small the response is a set of <strong>parallel arrays</strong> rather than one object per satellite: index <code>i</code> of each array describes the same object.100              </p>101            </Prose>102            <Code label="Fields">{`{103  "t0": "…", "t1": "…", "step_s": 60, "count": N, "total_tracked": N,104  "fields": ["norad", "cls", "mission", "active", "pos", "vel"],105  "norad":   [25544, …],            // NORAD id per object106  "cls":     [0, …],                // index into legend.cls   (orbit class)107  "mission": [8, …],                // index into legend.mission108  "active":  [1, …],                // 1 = ACTIVE status109  "pos":     [lat0, lon0, alt0, lat1, lon1, alt1, …],   // 6 numbers per object (deg, deg, km)110  "vel":     [v0, v1, …],           // km/s at t0 and t1111  "legend":  { "cls": ["LEO","MEO","GEO","HEO","OTHER"], "mission": [ … ] }112}`}</Code>113            <Prose className="mt-3">114              <p>Positions are derived from the latest element set of each object and inherit its age; check <code>/sources/status</code> for the median element age before relying on them.</p>115            </Prose>116          </DocSection>117118          <DocSection id="rate-limits" title="Rate limits">119            <Prose>120              <p>Limits are applied per client IP over a sliding 60-second window. Exceeding a bucket returns <code>429 Too Many Requests</code> with the bucket name in the error detail. Cache responses on your side; most datasets change hourly at most.</p>121            </Prose>122            <div className="mt-4 overflow-x-auto">123              <table className="data-table stack sm:min-w-[480px]">124                <thead>125                  <tr>126                    <th>Bucket</th>127                    <th className="num">Requests / min</th>128                    <th>Applies to</th>129                  </tr>130                </thead>131                <tbody>132                  {RATE_LIMITS.map((r) => (133                    <tr key={r.bucket}>134                      <td className="primary mono text-sm">{r.bucket}</td>135                      <td data-label="Requests / min" className="num tnum text-sm">136                        {r.perMinute}137                      </td>138                      <td data-label="Applies to" className="mono text-xs text-ink-2">139                        {r.applies}140                      </td>141                    </tr>142                  ))}143                </tbody>144              </table>145            </div>146          </DocSection>147148          <DocSection id="attribution" title="Attribution and terms">149            <Prose>150              <p>151                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: <strong>“Orbital data courtesy of CelesTrak.”</strong> Derived values (orbit class, mission type, constellation membership, activity score, density) should be credited to SatelliteIndex with a link to the <Link href={routes.methodology()} className="link">methodology</Link>. The full source list with licenses is on <Link href={routes.sources()} className="link">/sources</Link>.152              </p>153              <p>{ATTRIBUTION}</p>154            </Prose>155            <Callout tone="warn">{DISCLAIMER}</Callout>156            <p className="mt-3 text-sm text-ink-3">157              Full <Link href={routes.terms()} className="link">terms of use</Link>.158            </p>159          </DocSection>160161          <DocSection id="roadmap" title="Keys and tiers">162            <Prose>163              <p>164                The API is <strong>free during the MVP</strong> 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 <a href={`mailto:${CONTACT_EMAIL}`} className="link">{CONTACT_EMAIL}</a> so we can warn you before any breaking change.165              </p>166            </Prose>167          </DocSection>168        </div>169      </DocLayout>170    </Container>171  );172}173