Skip to main content

Python client for the Goable API — suitability scoring for outdoor activities.

Project description

goable-sdk

PyPI CI License: MIT

Python client for the Goable API — 0-100 suitability scoring for outdoor activities (water, snow, air, land) from real-time weather and multi-domain physics.

Thin, typed transport over the public tenant-facing REST surface. Sync only (built on httpx) in v0.1.0. Python 3.10+.

This is the Python sibling of @goable-io/sdk (TypeScript) — same methods, same semantics, snake_case instead of camelCase.

Install

pip install goable-sdk

Quickstart

import os
from goable_sdk import GoableClient

goable = GoableClient(api_key=os.environ["GOABLE_API_KEY"])

# Score a single activity at a location + time window
result = goable.score({
    "activity": "kitesurfing",
    "location": {"lat": 43.7, "lng": 7.27},
    "window": {"from": "2026-06-01T06:00:00Z", "to": "2026-06-01T18:00:00Z"},
})

result.score       # 0-100
result.verdict      # "unsafe" | "poor" | "marginal" | "fair" | "favorable" | "excellent"
result.confidence

Inverse query — "where should I go?" — ranks sub-spots for an activity within a region:

spots = goable.recommend_spot({
    "activity": "kitesurfing",
    "region": {"center": {"lat": 43.7, "lng": 7.27}, "radiusKm": 50},
    "window": {"from": "2026-06-01T06:00:00Z", "to": "2026-06-01T18:00:00Z"},
    "topK": 5,
})

Use it as a context manager to close the underlying connection pool deterministically:

with GoableClient(api_key=os.environ["GOABLE_API_KEY"]) as goable:
    result = goable.score({"activity": "kitesurfing", "location": {"lat": 43.7, "lng": 7.27}})

Authentication

Every request carries your tenant API key. The canonical production header is X-Goable-Key, which the client sends automatically:

GoableClient(api_key="gk_...")   # -> sends "X-Goable-Key: gk_..."

The API also accepts Authorization: Bearer <key> as a legacy fallback for direct testing, but new integrations should use the default X-Goable-Key path. (Production traffic sits behind CloudFront, which reserves the Authorization header for its own signature — the custom header sidesteps that.)

Mint a key from the tenant portal at console.goable.io/portal/keys.

Configuration

GoableClient(
    api_key="...",                    # required — sent as X-Goable-Key
    base_url="https://api.goable.io", # default
    timeout=30.0,                     # seconds; default 30, 0/None disables
    client=custom_httpx_client,       # default: an owned httpx.Client(); inject for tests
)

Request bodies and query params

Every method accepts either a plain dict or the matching generated pydantic model as the request body (ScoreRequest-shaped code lives in goable_sdk._models as V1ScorePostRequest, etc. — see below). A plain dict is usually the path of least resistance:

goable.score({"activity": "kitesurfing", "location": {"lat": 43.7, "lng": 7.27}})

Query-string parameters (list_policies, recent_observations, sustainability_index, verification_export, audit_export) are always a plain Mapping[str, Any] — several of the underlying query schemas alias a field to the Python keyword from (e.g. audit_export's date range), which is far more natural to write as {"from": ..., "to": ...} than to fight a generated model's alias-only constructor for.

Methods

The client mirrors the full public tenant-facing surface — one method per OpenAPI path (camelCase in the TS SDK becomes snake_case here). Grouped by area:

Score

Method Endpoint Notes
score(input) POST /v1/score ensemble: true → probabilistic (Pro+); rider_skill_level skill-conditioned (Pro+)
score_series(input) POST /v1/score/series per-step over a window
score_multi(input) POST /v1/score/multi many activities, one location
score_historical(input) POST /v1/score/historical climatology percentiles (Pro+)
score_portfolio(input) POST /v1/score/portfolio multi-spot joint variance
score_difficulty(input) POST /v1/score/difficulty L15 skill-conditioned difficulty grids (Pro+)
explain_counterfactual(input) POST /v1/score/explain-counterfactual binding constraint, sensitivities, best window/spot
report_outcome(session_id, input, idempotency_key=None) POST /v1/score/{sessionId}/outcome close the calibration loop

Recommend

Method Endpoint Notes
recommend_spot(input) POST /v1/recommend-spot inverse query: top-K ranked sub-spots

Decision

Method Endpoint Notes
decision(input) POST /v1/decision personalized go/no-go (Pro+)
delete_user_data(pseudonym) DELETE /v1/decision/user-data/{pseudonym} GDPR erasure; returns receipt headers

Intelligence

Method Endpoint Notes
explain(input) POST /v1/intelligence/explain LLM narrative (Pro+)
briefing(input) POST /v1/intelligence/briefing LLM briefing (Pro+)
edge_case(input) POST /v1/intelligence/edge-case LLM edge-case narrative for a marginal score

Projections (Scale)

Method Endpoint Notes
projections(input) POST /v1/projections single-spot climate-decadal
projections_portfolio(input) POST /v1/projections/portfolio multi-spot
adaptation_report(input) POST /v1/projections/adaptation-report months × scenarios × decades

Underwriting (Scale)

Method Endpoint Notes
quote(input) POST /v1/underwriting/quote parametric premium
get_quote(id) GET /v1/underwriting/quote/{id} fetch a stored quote
bind_policy(input, idempotency_key=None) POST /v1/underwriting/policy/bind bind a quote; 422 DRIFT_ACTIVEDriftActiveError
list_policies(query=None) GET /v1/underwriting/policy paginated, boundAt DESC
get_policy(policy_id) GET /v1/underwriting/policy/{policyId} policy + payout events
evaluate_policy(policy_id) POST /v1/underwriting/policy/{policyId}/evaluate re-evaluate; bodyless
settle_policy(policy_id, input) POST /v1/underwriting/policy/{policyId}/settle platform-ops only

Observations / nowcasting (L5.3)

Method Endpoint Notes
create_station(input) POST /v1/observations/stations register a station
list_stations() GET /v1/observations/stations the tenant's stations
update_station(station_id, input) PATCH /v1/observations/stations/{stationId} partial update
recent_observations(station_id, query=None) GET /v1/observations/stations/{stationId}/recent most-recent observations
submit_observations(input) POST /v1/observations push into the 0-6h window (Pro+)
submit_outcome(input) POST /v1/outcomes standalone outcome (not tied to a scored session)

Audit & compliance

Method Endpoint Notes
audit_export(query) GET /v1/audit/export query["format"] == "csv"str, else parsed JSON

LLM BYOK (bring-your-own Anthropic key)

Method Endpoint Notes
set_llm_key(input) PUT /v1/tenant/llm-key validate + store; resolves None (204)
get_llm_key() GET /v1/tenant/llm-key masked status (never the key)
delete_llm_key() DELETE /v1/tenant/llm-key remove; resolves None (204)

Health, legal & public (no auth)

Method Endpoint Notes
health() GET /v1/health liveness
health_ready() GET /v1/health/ready readiness (503 → GoableAPIError)
legal_document(kind) GET /v1/legal/{kind}/current current published legal doc
catalog_stats() GET /v1/public/catalog-stats open catalogue coverage stats
sustainability_index(query) GET /v1/public/sustainability-index Goable Sustainability Index (JSON-LD)
public_signup(input) POST /v1/public/signup self-service tenant signup

Research (open data, CC BY — NDJSON streams returned as str)

Method Endpoint Notes
difficulty_atlas_export() GET /v1/research/difficulty-atlas/export.jsonl L15 Difficulty Atlas
verification_export(query=None) GET /v1/research/verification/export Stream F forecast verification

Full endpoint reference: goable.io/docs.

Errors

from goable_sdk import GoableAPIError, GoableNetworkError

try:
    goable.score({"activity": "kitesurfing", "location": {"lat": 43.7, "lng": 7.27}, "ensemble": True})
except GoableAPIError as err:
    err.status                # e.g. 402
    err.code                  # e.g. "PAYMENT_REQUIRED"
    err.issues                # Zod issues on 422 VALIDATION_ERROR
    err.detail                # free-form context (e.g. plan info)
    err.retry_after_seconds   # seconds from `Retry-After` on a 429 (else None)
    err.rate_limit            # {"limit", "remaining", "reset"} when the response carried X-RateLimit-* headers
except GoableNetworkError as err:
    err.kind  # "timeout" | "network" | "parse"

On a 429, back off using the server's hint:

import time

try:
    ...
except GoableAPIError as err:
    if err.status == 429 and err.retry_after_seconds is not None:
        time.sleep(err.retry_after_seconds)
        # ...then retry

DriftActiveError (a GoableAPIError subclass) is raised on 422 DRIFT_ACTIVE from bind_policy() and exposes open_drift_events.

Testing without network access

The client never hits the network directly in your own tests either — inject an httpx.Client built on httpx.MockTransport:

import httpx
from goable_sdk import GoableClient

def handler(request: httpx.Request) -> httpx.Response:
    return httpx.Response(200, json={"status": "ok"})

client = GoableClient(api_key="test-key", client=httpx.Client(transport=httpx.MockTransport(handler)))

Types are generated from the API contract

The request/response models in goable_sdk._models (re-exported from the top-level package, and fully available via goable_sdk.models) are generated from the Goable API's OpenAPI document (openapi.json) via datamodel-code-generator — they are never hand-authored, so they can't drift from the contract. Never hand-edit src/goable_sdk/_models.py; regenerate it with:

python scripts/generate_models.py

The committed openapi.json tracks the live public API contract.

Contributing & releases

Releases are automated via PyPI Trusted Publishing: merging to main (with a version bump in pyproject.toml) publishes to PyPI on tag.

License

MIT © Fabio Carucci

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

goable_sdk-0.1.0.tar.gz (42.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

goable_sdk-0.1.0-py3-none-any.whl (25.9 kB view details)

Uploaded Python 3

File details

Details for the file goable_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: goable_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 42.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for goable_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c9f65e48b783cec81c8a643c5736f94426cf7f71243d13f7bb22363ff5a5370f
MD5 9739fc9dbe3e6a8240d56bb153cb4c12
BLAKE2b-256 7f86e8ac77139bc6240c2637773125035a34bd0bc7efe19dc5b6b0632b0a5dda

See more details on using hashes here.

Provenance

The following attestation bundles were made for goable_sdk-0.1.0.tar.gz:

Publisher: release.yml on goable-io/python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file goable_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: goable_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 25.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for goable_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 456e0a4234bdab7104e15b7f1da6d7bdfb3eb9e02fd3063560d3df0d87996b39
MD5 9980507a538598906768d59fdf441446
BLAKE2b-256 f35d6bc1f6fd25a46c8d44b5ead5d64379338c8e6bd7d9eb9cae931a6516a996

See more details on using hashes here.

Provenance

The following attestation bundles were made for goable_sdk-0.1.0-py3-none-any.whl:

Publisher: release.yml on goable-io/python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page