# Price alerts

Create, list, update, and cancel price alerts with the Rust AlertsService.

> **In development, coming soon**
>
> Price alerts are not yet available on Testnet: these calls fail there until the service is routed. This page documents the SDK surface so you can prepare for it.

`client.alerts` manages price alerts. An alert watches one market's last traded price or index price and fires once when the price crosses its level. `Above` fires at or above the level, `Below` at or below it. A fired alert is delivered in-app, by Telegram, or both.

Alerts belong to the Main Account: use an API key of the Main Account. A key scoped to a sub-account is refused with `Error::PermissionDenied` and code `SUBACCOUNT_KEY_REFUSED`.

## Methods

- **`create(params)`**\
  Create an alert; returns `PriceAlert`
- **`list(opts)`**\
  One page of alerts, newest first; returns `PriceAlertsPage`
- **`all(opts)`**\
  A stream over every page
- **`update(params)`**\
  Change direction, price, or channels; returns `UpdateAlertResult`
- **`cancel(alert_id)`**\
  Cancel an alert; returns the `PriceAlert`

## Create an alert

```rust
use polyester::models::{AlertChannel, AlertDirection, AlertPriceSource, CreateAlertParams};
use polyester::Price;

let params = CreateAlertParams::new(
    "BTC-USDT",
    AlertDirection::Above,
    Price::from_decimal_str("100000", Some("BTC-USDT".into()))?,
    vec![AlertChannel::InApp],
)
.with_price_source(AlertPriceSource::LastPrice)
.with_idempotency_key("btc-above-100k");
let alert = client.alerts.create(params).await?;
println!("{} {} armed={}", alert.alert_id, alert.status, alert.armed);
```

Prices are exact `Price` values, as for orders. Without a price source, the alert watches the index price where the market has one, and the last traded price otherwise. The current price is recorded as `reference_price`. An alert whose condition already holds starts unarmed and fires only after the price moves away and crosses back.

The input types are built with constructors (`CreateAlertParams::new`, `for_symbol_id`, `UpdateAlertParams::new`) and `with_...` setters, because fields may be added in minor releases. `ListAlertsOptions` starts from `Default::default()`.

## List, update, and cancel

```rust
use polyester::models::{AlertStatus, ListAlertsOptions, UpdateAlertParams};
use polyester::Price;

let mut opts = ListAlertsOptions::default();
opts.statuses = vec![AlertStatus::Active, AlertStatus::Triggered];
let page = client.alerts.list(opts).await?;

if let Some(alert) = page.alerts.first() {
    let update = UpdateAlertParams::new(alert.alert_id.clone(), alert.revision)
        .with_price(Price::from_decimal_str("105000", None)?)
        .with_idempotency_key("btc-above-105k");
    let result = client.alerts.update(update).await?;
    println!("{} {}", result.outcome, result.alert.revision);

    client.alerts.cancel(&result.alert.alert_id).await?;
}
```

- `update` sends only the fields you set, with the alert's last-read `revision` as `expected_revision`. A stale revision is refused with `REVISION_CONFLICT`: read the alert again.
- An update that sets no field sets a fired alert again and leaves an active one unchanged.
- `outcome` is `"applied"`, `"unchanged"` or `"replayed"`. Tell a retry from a fresh change by `outcome`, not by `revision`.
- Statuses keep the API spelling `"cancelled"`, unlike the triggers' `canceled`.

## Errors and retries

Refusals keep their codes in `polyester::errors::alert_codes`:

- `PRICE_UNAVAILABLE`: the market's price could not be read. Nothing changed; it is retryable.
- `SERVICE_UNAVAILABLE`: the outcome of a create, update, or cancel is unknown. Send it again with the same idempotency key (cancel is idempotent), or read the alerts back.
- `REVISION_CONFLICT`, `IDEMPOTENCY_CONFLICT`, `ALERT_LIMIT_REACHED`, `ALERT_UPDATE_LIMIT_REACHED` and `ALERT_NOT_FOUND` are final.

## Related

- [Errors](https://testnet.polyester.com/docs/sdk/rust/reference/errors)
- [Social verification](https://testnet.polyester.com/docs/sdk/rust/reference/social-verification)
