Metadata-Version: 2.4
Name: elastly
Version: 0.2.0
Summary: Elastly API
Home-page: https://elastly.io/docs/sdk
Author: Elastly
Author-email: Elastly <support@elastly.io>
License: MIT
Project-URL: Repository, https://github.com/elastly/elastly-python
Keywords: OpenAPI,OpenAPI-Generator,Elastly API
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: urllib3<3.0.0,>=2.1.0
Requires-Dist: python-dateutil>=2.8.2
Requires-Dist: pydantic>=2.11
Requires-Dist: typing-extensions>=4.7.1
Dynamic: author
Dynamic: home-page
Dynamic: license-file

# Elastly Python SDK

The official Python client for the [Elastly](https://elastly.io) REST API.

Elastly prices quote and order lines for B2B sellers. Send a line (product, customer, quantity) and get back a price with the reasoning behind it: which pricing rules fired, what each one added, and how confident the engine is. The engine learns from the outcomes you feed back, so prices improve over time.

This client covers the full v1 API:

- **Prices**: price up to 100 lines in one call.
- **Ingest**: push products, customers, quotes, and orders from your own system in staged batches.
- **Write-backs**: pull approved price changes into your system and confirm them once applied.

## Install

```bash
pip install elastly
```

Requires Python 3.9 or newer.

## Authenticate

Create an API key in the dashboard (Settings, then API keys). Keys are scoped: an `erp` key prices lines, a `connector` key ingests data and claims write-backs. Send the key as a bearer token.

## Price a line

```python
import uuid

import elastly

configuration = elastly.Configuration(access_token="elastly_live_...")

with elastly.ApiClient(configuration) as api_client:
    prices = elastly.PricesApi(api_client)

    response = prices.get_prices(
        idempotency_key=str(uuid.uuid4()),
        prices_request=elastly.PricesRequest(
            lines=[
                elastly.PricesRequestLinesInner(
                    product_sku="SKU-1042",
                    customer_external_id="CUST-88",
                    quantity=25,
                )
            ]
        ),
    )

    for line in response.lines:
        result = line.actual_instance
        if result.ok:
            print(result.price_cents, result.currency, result.reason_summary)
        else:
            print("failed:", result.code, result.message)
```

Every response line is either a priced result (`ok` is true) or a per-line error. One bad line never fails the batch.

## Retries and idempotency

`POST /v1/prices` requires an `Idempotency-Key` header. Use a fresh key for each logical request and reuse the same key on retries. A replay returns the stored response, so a retried call never prices twice.

## Price with a deadline and a fallback

If you price inside a checkout or quote flow, use the serving layer instead of calling the API directly. It puts a hard deadline on the call (800ms by default), retries transient failures with backoff, trips a circuit breaker when Elastly is down (5 failures, 30 second cooldown), and falls back to a price you supply instead of blocking your flow.

```python
import elastly
from elastly.serving import FallbackPrice, PricesNamespace, static_fallback
from elastly.transport import ElastlyTransport

configuration = elastly.Configuration(access_token="elastly_live_...")
client = elastly.ApiClient(configuration)
prices = PricesNamespace(ElastlyTransport(client))

result = prices.get(
    elastly.PricesRequestLinesInner(product_sku="SKU-1042", quantity=25),
    fail_open=static_fallback(lambda line, cause: FallbackPrice(price_cents=1500)),
)

if result.source == "elastly":
    print(result.price_cents, result.reason_summary)
elif result.source == "fallback":
    print(result.price_cents, "fallback because", result.cause.reason)
else:
    print("no price available:", result.cause.reason)
```

Every result carries a `source`: `"elastly"` (a real price), `"fallback"` (your resolver's price), or `"unavailable"` (your resolver returned `None`). Mistakes on your side, like an invalid request or a bad key, always raise a typed error from `elastly.errors` instead of falling back. Prefer an exception over a fallback price? Pass `fail_open=throw_on_failure()`.

The transport handles retries and idempotency keys for you, so you do not need to manage the `Idempotency-Key` header yourself on this path.

## Verify webhooks

Verify the `elastly-signature` header on every webhook before trusting the payload:

```python
from elastly.webhooks import SignatureVerificationError, WebhooksNamespace

webhooks = WebhooksNamespace()
try:
    event = webhooks.verify(raw_body, signature_header, "whsec_...")
except SignatureVerificationError:
    ...  # reject with a 400
print(event.event, event.data)
```

`verify` checks the HMAC signature in constant time, rejects replays older than 5 minutes, and returns a typed event.

## Close the loop

Elastly learns from the difference between the price it recommended and the price your team actually charged. When you ingest quotes, echo the `pricingDecisionId` you received from the prices call on the matching quote line. Skip that and the engine has nothing to learn from.

## Documentation

- [API reference](https://elastly.io/docs/api)
- Per-endpoint docs for this client live in [docs](./docs).

## About this repository

This code is generated from the Elastly OpenAPI contract, so pull requests against generated files get overwritten by the next release. If something is broken or missing, open an issue or email support@elastly.io.

## License

[MIT](./LICENSE)
