# Price alerts

Create, list, update, and cancel price alerts with the Go 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 price, `"below"` at or below it. A fired alert is delivered in-app, to the account's linked Telegram account, 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 an `*errors.AuthError` whose `errors.AlertErrorCode` is `SUBACCOUNT_KEY_REFUSED`.

## Methods

- **`Create(ctx, services.CreateAlertParams)`**: Create an alert on one market (`Symbol` or `SymbolID`). Returns `models.PriceAlert`.
- **`List(ctx, services.ListAlertsParams)`**: One page of your alerts, newest first. Returns `models.PriceAlertsPage`.
- **`All(ctx, services.ListAlertsParams)`**: Every alert matching the `List` filters, fetching pages as needed.
- **`Update(ctx, services.UpdateAlertParams)`**: Change the direction, price, or channels. Returns `models.UpdatePriceAlertResult`.
- **`Cancel(ctx, alertID)`**: Cancel an alert. Returns `models.PriceAlert`.

### Create

```go
symbol := "BTC-USDT"
alert, err := client.Alerts.Create(ctx, services.CreateAlertParams{
    Symbol:         &symbol,
    Direction:      "above",
    Price:          models.PriceFromDecimal("70000"),
    Channels:       []string{"in_app", "telegram"},
    PriceSource:    "last", // or "index"; empty lets the server choose
    IdempotencyKey: "btc-70k-alert",
})
if err != nil { log.Fatal(err) }
fmt.Println(alert.AlertID, alert.Status, alert.Armed, alert.ReferencePrice.Ticks())
```

`Price` is a `models.PriceInput`, as for orders; reads return `models.PriceTicks`. An empty `PriceSource` uses the index price where the market has one, the last traded price otherwise; the returned alert states it. The current price is recorded as `ReferencePrice`; an alert whose condition already holds starts with `Armed` false and fires only after the price moves back and crosses again. `IdempotencyKey` makes a retry of the create safe.

### List

```go
page, err := client.Alerts.List(ctx, services.ListAlertsParams{
    Statuses: []string{"active"},
    Symbol:   &symbol,
    Limit:    50,
})
if err != nil { log.Fatal(err) }
for _, item := range page.Alerts {
    fmt.Println(item.AlertID, item.Direction, item.Price.Ticks(), item.Status)
}

for item, err := range client.Alerts.All(ctx, services.ListAlertsParams{Statuses: []string{"triggered"}}) {
    if err != nil { log.Fatal(err) }
    fmt.Println(item.AlertID, item.TriggeredAt, item.TriggeredPrice)
}
```

`Statuses` takes `"active"`, `"triggered"`, and `"cancelled"`. Note the spelling: alerts use `"cancelled"`, unlike a trigger's `"canceled"`. `Limit` is at most 200; 0 lets the server use 50. Pass `NextPageToken` back as `PageToken` with the same filters.

### Update and cancel

```go
newPrice := models.PriceFromDecimal("72000")
result, err := client.Alerts.Update(ctx, services.UpdateAlertParams{
    AlertID:          alert.AlertID,
    ExpectedRevision: alert.Revision,
    Price:            &newPrice,
    IdempotencyKey:   "btc-72k-alert",
})
if err != nil { log.Fatal(err) }
fmt.Println(result.Outcome, result.Alert.Revision)

canceled, err := client.Alerts.Cancel(ctx, alert.AlertID)
if err != nil { log.Fatal(err) }
fmt.Println(canceled.Status)
```

`Update` sends only the fields you set. With none, it sets a triggered alert again as it is. `ExpectedRevision` is the `Revision` from your last read and is required; a stale one is refused with `REVISION_CONFLICT`. `Outcome` is `"applied"`, `"unchanged"`, or `"replayed"` (the idempotency key was already used for this request): tell a retry from a fresh change by `Outcome`, never by `Revision`. Cancelling a cancelled alert returns it unchanged, so a retry is safe.

## Errors and retries

Read a refusal's contract code with `errors.AlertErrorCode(err)` and compare it with the `errors.AlertCode*` constants.

- **`PRICE_UNAVAILABLE`**: A retryable `*errors.ServerError`; nothing changed.
- **`SERVICE_UNAVAILABLE`** on `Create`, `Update`, or `Cancel`: a `*errors.ServerError` whose outcome is unknown. Send a create or update again with the same `IdempotencyKey`, cancel again, or read the alerts back.
- **`IDEMPOTENCY_CONFLICT`**: The key was already used for another request; the original stands.
- **`ALERT_TRIGGERED`** / **`ALERT_CANCELLED`**: The alert already fired or was cancelled.

## Related

- [Errors](https://testnet.polyester.com/docs/sdk/go/reference/errors)
- [Social verification](https://testnet.polyester.com/docs/sdk/go/reference/social-verification) for the Telegram link
