Metadata-Version: 2.4
Name: orbsim-sdk
Version: 0.1.2
Summary: Python SDK for the OrbSim API
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.8.0

# OrbSim SDK

[![PyPI](https://img.shields.io/pypi/v/orbsim-sdk.svg)](https://pypi.org/project/orbsim-sdk/)
[![Python](https://img.shields.io/pypi/pyversions/orbsim-sdk.svg)](https://pypi.org/project/orbsim-sdk/)

Typed Python client for the OrbSim API.

OrbSim is a Python-first orbit operations and mission analysis service. The SDK wraps the REST API with sync and async clients, Pydantic request/response models, bearer-token authentication, and helpers for common mission workflows: projects, satellites, ground stations, propagation, contacts, link budgets, constellations, public TLE lookup, and admin operations.

Package: [orbsim-sdk on PyPI](https://pypi.org/project/orbsim-sdk/)

## Installation

OrbSim SDK requires Python 3.11 or newer.

```bash
python -m pip install orbsim-sdk
```

The package installs:

- `httpx` for HTTP transport
- `pydantic` for typed request and response models

## API URL

Create clients with the OrbSim API base URL, including `/api`.

```python
BASE_URL = "http://127.0.0.1:8000/api"
```

When running OrbSim locally, start the backend first:

```bash
uvicorn app.main:app --reload
```

The FastAPI OpenAPI docs are available at:

```text
http://127.0.0.1:8000/docs
```

## Quick Start

```python
from orbsim_sdk import (
    OrbitSource,
    OrbsimClient,
    ProjectCreate,
    SatelliteCreate,
    TLEDefinition,
)

client = OrbsimClient("http://127.0.0.1:8000/api")

client.login(
    "admin@example.com",
    "supersecret123",
    token_name="SDK Session",
)

project = client.create_project(
    ProjectCreate(
        name="LEO Demo",
        description="Created from the Python SDK",
    )
)

satellite = client.create_satellite(
    project.id,
    SatelliteCreate(
        name="ISS Demo",
        source=OrbitSource.TLE,
        tle=TLEDefinition(
            line1="1 25544U 98067A   26099.50000000  .00016717  00000+0  30747-3 0  9991",
            line2="2 25544  51.6400  54.1200 0003870 123.4567 321.6543 15.50000000123456",
        ),
    ),
)

state = client.get_satellite_state(
    project.id,
    satellite.id,
    "2026-04-09T12:00:00Z",
)

print(state.latitude_deg, state.longitude_deg, state.altitude_m)
```

Use the client as a context manager when you want HTTP connections closed automatically:

```python
from orbsim_sdk import OrbsimClient

with OrbsimClient("http://127.0.0.1:8000/api") as client:
    client.login("admin@example.com", "supersecret123")
    print(client.me().email)
```

## Authentication

Private OrbSim endpoints use bearer tokens. Public endpoints under `/public/*` do not require authentication.

### Bootstrap the first admin

Use this once on a fresh OrbSim installation. The returned token is stored on the client automatically.

```python
from orbsim_sdk import OrbsimClient

client = OrbsimClient("http://127.0.0.1:8000/api")

auth = client.bootstrap_admin(
    email="admin@example.com",
    password="supersecret123",
    full_name="Mission Admin",
)

print(auth.user.email)
print(auth.token)
```

### Login

```python
auth = client.login(
    "admin@example.com",
    "supersecret123",
    token_name="Notebook Session",
)

print(auth.user.full_name)
```

### Use an existing token

```python
from orbsim_sdk import OrbsimClient

client = OrbsimClient(
    "http://127.0.0.1:8000/api",
    token="orb_your_token_here",
)

profile = client.me()
print(profile.email)
```

### Create and revoke API tokens

```python
created = client.create_token("CI Pipeline")
print(created.token)

for token in client.list_tokens():
    print(token.name, token.token_prefix, token.revoked_at)

client.revoke_own_token(created.token_info.id)
```

## Projects and Assets

Projects hold mission assets such as satellites, ground stations, and constellations.

```python
from orbsim_sdk import GroundStationCreate, ProjectCreate

project = client.create_project(ProjectCreate(name="Operations Sandbox"))

station = client.create_groundstation(
    project.id,
    GroundStationCreate(
        name="Berlin GS",
        latitude_deg=52.52,
        longitude_deg=13.405,
        altitude_m=35,
        min_elevation_deg=10,
    ),
)

print(station.id)
```

List existing resources:

```python
projects = client.list_projects()
satellites = client.list_satellites(project.id)
groundstations = client.list_groundstations(project.id)
```

Delete resources:

```python
client.delete_groundstation(project.id, station.id)
client.delete_project(project.id)
```

## Satellites

OrbSim supports TLE, Keplerian, and ephemeris-defined satellites.

### TLE satellite

```python
from orbsim_sdk import OrbitSource, SatelliteCreate, TLEDefinition

satellite = client.create_satellite(
    project.id,
    SatelliteCreate(
        name="ISS",
        source=OrbitSource.TLE,
        tle=TLEDefinition(
            line1="1 25544U 98067A   26099.50000000  .00016717  00000+0  30747-3 0  9991",
            line2="2 25544  51.6400  54.1200 0003870 123.4567 321.6543 15.50000000123456",
        ),
    ),
)
```

### Keplerian satellite

```python
from datetime import datetime, timezone

from orbsim_sdk import (
    AnomalyKind,
    KeplerianDefinition,
    OrbitSource,
    SatelliteCreate,
)

satellite = client.create_satellite(
    project.id,
    SatelliteCreate(
        name="Kepler Demo",
        source=OrbitSource.KEPLERIAN,
        keplerian=KeplerianDefinition(
            semi_major_axis_m=6_878_000,
            eccentricity=0.001,
            inclination_deg=97.4,
            raan_deg=15.0,
            arg_perigee_deg=30.0,
            anomaly_deg=45.0,
            anomaly_kind=AnomalyKind.TRUE,
            epoch=datetime(2026, 4, 9, 12, 0, tzinfo=timezone.utc),
        ),
    ),
)
```

### Ephemeris satellite

```python
from datetime import datetime, timezone

from orbsim_sdk import CartesianState, OrbitSource, SatelliteCreate

satellite = client.create_satellite(
    project.id,
    SatelliteCreate(
        name="Ephemeris Demo",
        source=OrbitSource.EPHEMERIS,
        ephemeris=[
            CartesianState(
                timestamp=datetime(2026, 4, 9, 12, 0, tzinfo=timezone.utc),
                position_m=[6_878_000, 0, 0],
                velocity_mps=[0, 7_610, 0],
            ),
            CartesianState(
                timestamp=datetime(2026, 4, 9, 12, 1, tzinfo=timezone.utc),
                position_m=[6_876_000, 456_000, 0],
                velocity_mps=[-505, 7_606, 0],
            ),
        ],
    ),
)
```

## Propagation and Mission Analysis

### State at a timestamp

```python
state = client.get_satellite_state(
    project.id,
    satellite.id,
    "2026-04-09T12:00:00Z",
)

print(state.position_m)
print(state.velocity_mps)
print(state.latitude_deg, state.longitude_deg, state.altitude_m)
```

### State samples over a time range

```python
from datetime import datetime, timezone

from orbsim_sdk import TimeRangeQuery

states = client.get_satellite_states(
    project.id,
    satellite.id,
    TimeRangeQuery(
        start=datetime(2026, 4, 9, 12, 0, tzinfo=timezone.utc),
        end=datetime(2026, 4, 9, 13, 0, tzinfo=timezone.utc),
        step_seconds=120,
    ),
)

print(len(states))
```

### Attitude and subsystem state

```python
attitude = client.get_satellite_attitude(
    project.id,
    satellite.id,
    "2026-04-09T12:00:00Z",
)

subsystems = client.get_satellite_subsystems(
    project.id,
    satellite.id,
    "2026-04-09T12:00:00Z",
)

print(attitude.euler_deg)
print(subsystems.battery_soc)
```

## Contacts and Link Budgets

### Satellite-to-ground contacts

```python
from datetime import datetime, timezone

from orbsim_sdk import TimeRangeQuery

contacts = client.get_contacts(
    project.id,
    satellite.id,
    station.id,
    TimeRangeQuery(
        start=datetime(2026, 4, 9, 0, 0, tzinfo=timezone.utc),
        end=datetime(2026, 4, 9, 6, 0, tzinfo=timezone.utc),
        step_seconds=60,
    ),
)

for contact in contacts:
    print(contact.start, contact.end, contact.max_elevation_deg)
```

### RF or optical link budget

```python
from datetime import datetime, timezone

from orbsim_sdk import LinkBudgetRequest, LinkKind

budget = client.get_link_budget(
    project.id,
    LinkBudgetRequest(
        source_satellite_id=satellite.id,
        groundstation_id=station.id,
        kind=LinkKind.RF,
        timestamp=datetime(2026, 4, 9, 12, 0, tzinfo=timezone.utc),
        frequency_hz=8.2e9,
        tx_power_dbw=10,
        tx_gain_dbi=25,
        rx_gain_dbi=25,
        bandwidth_hz=1e6,
        system_temperature_k=500,
    ),
)

print(budget.visible, budget.snr_db, budget.range_m)
```

## Constellations

### Create a constellation from existing satellites

```python
from orbsim_sdk import ConstellationCreate

constellation = client.create_constellation(
    project.id,
    ConstellationCreate(
        name="Demo Mesh",
        satellite_ids=[satellite.id],
        isl_max_range_m=3_500_000,
        max_degree=4,
    ),
)
```

### Generate a Walker Delta constellation

```python
from orbsim_sdk import (
    ConstellationDesignCreate,
    ConstellationDesignType,
    WalkerDeltaDefinition,
)

design = client.design_constellation(
    project.id,
    ConstellationDesignCreate(
        name="Walker Demo",
        type=ConstellationDesignType.WALKER_DELTA,
        walker_delta=WalkerDeltaDefinition(
            total_satellites=12,
            plane_count=3,
            relative_spacing=1,
            semi_major_axis_m=6_878_000,
            eccentricity=0.001,
            inclination_deg=97.4,
            satellite_name_prefix="WALKER",
        ),
        isl_max_range_m=3_500_000,
        max_degree=4,
    ),
)

print(design.constellation.id)
print([sat.name for sat in design.satellites])
```

### Query constellation network visibility

```python
network = client.get_constellation_network(
    project.id,
    design.constellation.id,
    "2026-04-09T12:00:00Z",
    kind="rf",
)

print(network.nodes.keys())
print(network.adjacency)
```

## Public API

Public endpoints do not require login or an API token.

```python
from orbsim_sdk import GroundStationCreate, OrbsimClient, PublicContactQuery, PublicSatelliteQuery

client = OrbsimClient("http://127.0.0.1:8000/api")

tle = client.get_public_tle(PublicSatelliteQuery(norad_id="25544"))
print(tle.name)

state = client.get_public_state(
    PublicSatelliteQuery(name="ISS"),
    "2026-04-09T12:00:00Z",
)
print(state.latitude_deg, state.longitude_deg)

contacts = client.get_public_contacts(
    PublicContactQuery(
        satellite=PublicSatelliteQuery(norad_id="25544"),
        groundstation=GroundStationCreate(
            name="Berlin GS",
            latitude_deg=52.52,
            longitude_deg=13.405,
            altitude_m=35,
            min_elevation_deg=10,
        ),
        start="2026-04-09T12:00:00Z",
        end="2026-04-09T18:00:00Z",
        step_seconds=120,
    )
)

print(len(contacts))
```

## Async Client

`AsyncOrbsimClient` exposes the same methods as `OrbsimClient`, with `await`.

```python
import asyncio

from orbsim_sdk import AsyncOrbsimClient


async def main() -> None:
    async with AsyncOrbsimClient("http://127.0.0.1:8000/api") as client:
        await client.login(
            "admin@example.com",
            "supersecret123",
            token_name="Async SDK Session",
        )

        projects = await client.list_projects()
        print([project.name for project in projects])


asyncio.run(main())
```

## Error Handling

API errors raise `OrbsimAPIError`. Validation errors from request/response models are Pydantic errors.

```python
from orbsim_sdk import OrbsimAPIError, OrbsimClient

client = OrbsimClient("http://127.0.0.1:8000/api")

try:
    client.login("admin@example.com", "wrong-password")
except OrbsimAPIError as exc:
    print(exc.status_code)
    print(exc.detail)
```

## Client Reference

### Authentication

| Method | Description |
| --- | --- |
| `bootstrap_admin(email, password, full_name)` | Create the first admin user and store the returned token on the client. |
| `register(email, password, full_name)` | Register a regular user and store the returned token. |
| `login(email, password, token_name="Default API Token")` | Authenticate and store the returned bearer token. |
| `me()` | Return the current authenticated user profile. |
| `list_tokens()` | List API tokens owned by the current user. |
| `create_token(name)` | Create a personal API token. |
| `revoke_own_token(token_id)` | Revoke one of the current user's tokens. |
| `set_token(token)` | Set or clear the bearer token used by the client. |

### Public endpoints

| Method | Description |
| --- | --- |
| `health()` | Check API and Orekit availability. |
| `get_public_tle(query)` | Fetch a public TLE by NORAD ID, name, or source URL. |
| `get_public_state(query, timestamp)` | Propagate a public satellite query to a timestamp. |
| `get_public_contacts(query)` | Compute public satellite contacts for a supplied ground station. |

### Project endpoints

| Method | Description |
| --- | --- |
| `create_project(payload)` | Create a project. |
| `list_projects()` | List projects visible to the current user. |
| `delete_project(project_id)` | Delete a project. |
| `create_satellite(project_id, payload)` | Create a TLE, Keplerian, or ephemeris satellite. |
| `list_satellites(project_id)` | List project satellites. |
| `delete_satellite(project_id, satellite_id)` | Delete a project satellite. |
| `create_groundstation(project_id, payload)` | Create a ground station. |
| `list_groundstations(project_id)` | List project ground stations. |
| `delete_groundstation(project_id, groundstation_id)` | Delete a project ground station. |

### Analysis endpoints

| Method | Description |
| --- | --- |
| `get_satellite_state(project_id, satellite_id, timestamp)` | Compute one orbital state. |
| `get_satellite_states(project_id, satellite_id, payload)` | Compute sampled orbital states over a time range. |
| `get_satellite_attitude(project_id, satellite_id, timestamp)` | Compute attitude at a timestamp. |
| `get_satellite_subsystems(project_id, satellite_id, timestamp)` | Compute subsystem state at a timestamp. |
| `get_contacts(project_id, satellite_id, groundstation_id, payload)` | Compute satellite-to-ground contact windows. |
| `get_link_budget(project_id, payload)` | Compute RF or optical link budget. |

### Constellation endpoints

| Method | Description |
| --- | --- |
| `create_constellation(project_id, payload)` | Create a constellation from existing satellites. |
| `design_constellation(project_id, payload)` | Generate a Walker Delta constellation and satellites. |
| `list_constellations(project_id)` | List project constellations. |
| `delete_constellation(project_id, constellation_id)` | Delete a constellation. |
| `get_constellation_network(project_id, constellation_id, timestamp, kind="rf")` | Compute constellation network visibility. |

### Admin endpoints

Admin methods require an admin bearer token.

| Method | Description |
| --- | --- |
| `admin_dashboard()` | Return aggregate admin metrics. |
| `admin_list_users()` | List users. |
| `admin_list_projects()` | List projects with owner email. |
| `admin_user_tokens(user_id)` | List tokens for a user. |
| `admin_toggle_admin(user_id)` | Toggle admin status for a user. |
| `admin_revoke_token(token_id)` | Revoke any token as an admin. |

## Models

All request and response objects are Pydantic models exported from `orbsim_sdk`.

Common request models:

- `ProjectCreate`
- `SatelliteCreate`
- `TLEDefinition`
- `KeplerianDefinition`
- `CartesianState`
- `GroundStationCreate`
- `TimeRangeQuery`
- `LinkBudgetRequest`
- `ConstellationCreate`
- `ConstellationDesignCreate`
- `WalkerDeltaDefinition`
- `PublicSatelliteQuery`
- `PublicContactQuery`

Common enums:

- `OrbitSource`: `tle`, `ephemeris`, `keplerian`
- `AnomalyKind`: `true`, `mean`, `eccentric`
- `AttitudeMode`: `lvlh`, `nadir`, `sun_pointing`, `inertial`
- `LinkKind`: `rf`, `optical`
- `ConstellationDesignType`: `walker_delta`

Because models are Pydantic objects, they can be serialized for logs, notebooks, or tests:

```python
state = client.get_satellite_state(project.id, satellite.id, "2026-04-09T12:00:00Z")
print(state.model_dump())
print(state.model_dump_json(indent=2))
```

## Notes

- Pass timestamps as timezone-aware `datetime` objects or ISO 8601 strings such as `"2026-04-09T12:00:00Z"`.
- The sync client owns its internal `httpx.Client` unless you pass a custom client. Call `close()` or use a context manager.
- The async client owns its internal `httpx.AsyncClient` unless you pass a custom client. Use `async with` or call `aclose()`.
- Private endpoints require `Authorization: Bearer <token>`. The SDK sets this header automatically after `login()`, `register()`, or `bootstrap_admin()`.
- For the full REST schema, run OrbSim and open `/docs` on your API host.
