<!-- Source: https://docs.geopera.com/api-reference/sdks/python · Markdown for LLMs -->

# Python SDK

There are two Python clients, both generated from — or tracking — the same operation
registry, so neither can drift from the live API:

- **`geopera`** — a friendly, hand-curated client with high-level namespaces
  (`client.catalog`, `client.orders`, `client.processing`, `client.billing`). Start
  here for application code.
- **`geopera_kernel`** — a fully generated, low-level client with one typed method per
  operation. Use it when you want the complete operation surface, exactly typed, with
  no curation.

## Install

```bash
pip install geopera-py
```

## Authenticate

```python
import geopera

# Machine-to-machine (servers, pipelines) — recommended
client = geopera.Client.from_api_key("gp_live_...")

# Or via your Geopera login (interactive scripts)
client = geopera.Client.from_password("you@example.com", "password")
```

`from_api_key` exchanges the key for short-lived JWTs and refreshes them for you, so you
don't manage token lifetimes. `from_client_credentials` is also available for the OIDC
client-credentials grant.

## Search the catalog

```python
results = client.catalog.search(
    host="earthsearch-aws",
    collections=["sentinel-2-l2a"],
    bbox=[151.0, -34.0, 152.0, -33.0],
    datetime_range="2024-01-01T00:00:00Z/2024-12-31T23:59:59Z",
    cloud_cover_lte=20,
    limit=25,
)
for feature in results.features:
    print(feature.id, feature.properties["datetime"])
```

## Preview a price and place an order

```python
# Server-authoritative price preview (a read — nothing is charged)
quote = client.orders.estimate(
    data_product="phr",
    feature_collection=fc,   # a GeoJSON FeatureCollection over your AOI
)
print(quote["total_credits"], quote["total_aud"])

# Place the order
order = client.orders.create(
    workspace_id="your-project-id",
    data_product="phr",
    display_name="Sydney harbour archive",
    feature_collection=fc,
    params={"id": "scene-abc"},
    tags=["production"],
)

# Track to a terminal state
fetched = client.orders.get(order["results"][0]["id"])
print(fetched.status)
```

Other order methods: `client.orders.list(...)`, `client.orders.update_tags(id, tags)`,
`client.orders.cancel(id)`, and `client.orders.schema(data_product_id)` to introspect a
product's parameters.

## Processing

```python
# Discover available processes and their cost
processes = client.processing.list_processes()
cost = client.processing.cost(process_id, inputs={...})

# Run one
job = client.processing.execute(
    process_id,
    workspace_id="your-project-id",
    inputs={"source_item_id": "it_3a9c...", "...": "..."},
)

# Track it
status = client.processing.get_job(job["id"])
```

## Billing

```python
balance = client.billing.balance()
print(balance.credits)

# Attach a card via Stripe SetupIntent, then top up
intent = client.billing.setup_intent()        # confirm client-side with Stripe.js
result = client.billing.topup(credits=50000)   # charges the default card

# Automate it
client.billing.set_auto_topup(
    enabled=True,
    threshold_credits=5000,
    topup_amount_credits=50000,
)
```

If a top-up needs 3-D Secure, `topup` surfaces the `client_secret` to complete the
challenge — see [the 3-D Secure retry](/api-reference/guides/billing#the-3-d-secure-retry).

## Webhooks (event subscriptions)

Subscribe to platform events and stop polling. Each delivery is an HTTP POST signed
with an HMAC-SHA256 `X-Geopera-Signature` header so you can verify it came from
Geopera. Discover the full event catalogue with `event_types()`.

```python
sub = client.webhooks.subscribe(
    url="https://your-app.example.com/hooks/geopera",
    events=["order.fulfilled", "job.completed", "credits.low_balance"],
)
client.webhooks.list()
client.webhooks.event_types()   # discover available event types
```

On your receiver, verify the `X-Geopera-Signature` header against the subscription
secret before trusting a delivery:

```python
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")
```

Delivery is durable and at-least-once with retry/backoff, so make your handler
idempotent (dedupe on `X-Geopera-Event-ID`). See the
[Webhooks guide](/api-reference/guides/webhooks) for the full event catalogue, the
delivery headers, and delivery semantics.

## The generated low-level client

When you want the complete, exactly-typed surface — every operation, every input/output
model — use `geopera_kernel`. It's a standard `openapi-python-client` package: one
module per operation plus a model for each input and output.

```python
from geopera_kernel import AuthenticatedClient
from geopera_kernel.api.kernel_ops import items_search_org
from geopera_kernel.models import OrgItemSearchInput

client = AuthenticatedClient(base_url="https://api.geopera.com", token="<jwt>")
resp = items_search_org.sync_detailed(
    client=client,
    body=OrgItemSearchInput(limit=5),
)
print(resp.parsed)
```

Because it's generated from the kernel's reflected OpenAPI, adding an operation to the
platform adds a typed method here on the next regeneration — no hand-maintained types.

## Errors

Both clients raise on non-2xx responses, surfacing the
[`application/problem+json`](/api-reference/errors) body (`status`, `title`, `detail`).
Handle `402` for payment problems and `409`/`422` for conflicts and validation as
described in [Errors](/api-reference/errors).
