Skip to main content

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" | "not_feasible" | "poor" | "marginal" | "fair" | "favorable" | "excellent"
result.confidence

verdict distinguishes an impossible-but-not-dangerous read (not_feasible — e.g. no rideable wind) from an actually unsafe one (unsafe). scoreBasis ("forecast" / "gated" / "no_data") says how score was produced — a "gated" 0 still carries the full breakdown + physics payload, so you can see why.

Discover the catalogue's activity slugs at runtime (no API key required):

catalog = goable.activities()
[a.slug for a in catalog.activities]  # ["kitesurfing", "surfing", ...]

goable_sdk.KNOWN_ACTIVITY_SLUGS is a committed offline snapshot of the same slugs (handy for autocomplete/typo-catching without a network call), and goable_sdk.ActivitySlug is a plain str alias for the activity field — intentionally not a closed Literal, since a brand-new catalog activity should never fail type-checking just because this snapshot hasn't caught up yet. Treat activities() as the source of truth.

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, idempotency_key=None) POST /v1/outcomes standalone outcome (not tied to a scored session)
void_outcomes(input) POST /v1/outcomes/void recall a batch of reported outcomes; returns the voided count

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)
activities() GET /v1/activities base activity slugs (slug/display_name/family); GET /v1/profiles is an alias
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

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.7.0.tar.gz (64.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.7.0-py3-none-any.whl (36.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: goable_sdk-0.7.0.tar.gz
  • Upload date:
  • Size: 64.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for goable_sdk-0.7.0.tar.gz
Algorithm Hash digest
SHA256 0cd70cbac669989d22291e11651c913074b7bb9b0e12bb978d72a3aacad1e527
MD5 9ef47caba7f1c5ca58806f6973b6b586
BLAKE2b-256 117b3c5d52d53586fe9c5a958f8fbee0f8306f2a3afdc25a6ddd1a3dfbd94013

See more details on using hashes here.

Provenance

The following attestation bundles were made for goable_sdk-0.7.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.7.0-py3-none-any.whl.

File metadata

  • Download URL: goable_sdk-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 36.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for goable_sdk-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 10210749d06bd6a5ddeb4a9958dfd99daa57a7569db6c0f98ad222de88bd1deb
MD5 1b1c9c75b12b92a8d95c42cec42282a5
BLAKE2b-256 10190eba18a4df6c033ead29cd19e03419f8b42cf725a100fde6971c268e77eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for goable_sdk-0.7.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.

Release history Release notifications | RSS feed

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

This release

0.7.0 This release

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page