Docs
Product 1

Alerts & Intelligence Engine

Define price, technical and volume triggers once; ApexTick evaluates them on every fresh tick and delivers the event to email, signed webhooks, Telegram, Discord, Slack or a REST event log.

You don't need to keep a WebSocket open or poll for prices. Create a trigger with POST /v1/alerts; ApexTick evaluates it in the background on every accepted tick and delivers an event when the condition is met.

Endpoints

MethodPathPurpose
POST/v1/alertsCreate an alert trigger
GET/v1/alertsList active triggers and your quota usage
DELETE/v1/alerts/{id}Deactivate a trigger and free its quota slot
GET/v1/alerts/eventsDelivery history of fired alerts (90 days)
PlanActive alertsChannels
Hobbyist3email
Trader Pro50email, webhook, telegram, discord, slack, rest
Quant & Community250All 6 channels
EnterpriseUnlimitedAll 6 channels

Create an alert

POST/v1/alertsHobbyist+
Shell
curl -s -X POST "https://api.apextick.com/v1/alerts" \
  -H "apikey: $APEXTICK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "AAPL",
    "trigger_type": "technical",
    "metric": "rsi14",
    "condition": "lte",
    "threshold": 30,
    "channel": "telegram",
    "target": "@my_trading_channel",
    "cooldown_minutes": 60
  }'

Request fields

FieldTypeUsed byDescription
symbolstringAllInstrument in standard notation, e.g. AAPL, BTC-USD.
trigger_typestringAllprice, technical, volume, news or calendar.
metricstringprice, technical, volumeprice for price triggers, rvol20 for volume triggers, or a technical metric.
conditionstringprice, technical, volumegt, lt, gte, lte, crosses_above or crosses_below.
thresholdnumberprice, technical, volumeFixed reference value. Must be positive for price and volume triggers.
compare_metricstringprice, technicalCompare against another indicator instead of a fixed threshold. Use one or the other.
sentimentstringnewsbullish, bearish or neutral.
categorystringnewsearnings, macro, mergers, regulatory or technical_breakout.
eventstringcalendarearnings, dividend or split.
notice_hours_beforeintegercalendarNotice period, 1–168 hours. Default 24.
channelstringAllemail, webhook, telegram, discord, slack or rest.
targetstringAll except restWhere to deliver; see channels.
cooldown_minutesintegerAllMinimum time between deliveries, 1–10080. Default 60.

Response

201 Created returns the stored trigger. For the webhook channel the response includes a signing_secret. It is returned only once, so store it now; you need it to verify deliveries.

JSON
{
  "id": "6f1c2a9e-3b4d-4c8a-9e21-7d5b0c4e8f13",
  "symbol": "NVDA",
  "trigger_type": "volume",
  "metric": "rvol20",
  "condition": "crosses_above",
  "threshold": 2,
  "channel": "webhook",
  "target": "https://hooks.example.com/apextick",
  "signing_secret": "whsec_3f9a…",
  "cooldown_minutes": 60,
  "active": true,
  "created_at": "2026-10-16T13:05:12Z"
}

If you exceed your plan's alert quota or use a channel your plan doesn't include, the API returns 403 with the reason, for example {"error": "Active alert limit reached for current plan", "limit": 3}.

Trigger types

Price

Fires on the last traded price. Use metric: "price" with a positive threshold, or compare the price with a moving average using compare_metric:

JSON
{ "symbol": "TSLA", "trigger_type": "price", "metric": "price", "condition": "crosses_below", "threshold": 240, "channel": "email", "target": "you@example.com" }
JSON
{ "symbol": "SPY", "trigger_type": "price", "metric": "price", "condition": "crosses_above", "compare_metric": "sma200", "channel": "slack", "target": "https://hooks.slack.com/services/T000/B000/XXXX" }

Technical

Fires on a technical indicator. Available metrics:

MetricDescription
sma20, sma50, sma200Simple moving averages over 20, 50 and 200 sessions
rsi1414-period Relative Strength Index (0–100)
macd1414-day MACD value
macd, macd_signal, macd_histMACD 12/26/9 line, signal line and histogram on daily closes
atr1414-period Average True Range
stoch_k1414-period stochastic %K
volatility3030-day historical volatility

Classic setups:

JSON
{ "symbol": "AAPL", "trigger_type": "technical", "metric": "rsi14", "condition": "crosses_below", "threshold": 30, "channel": "discord", "target": "https://discord.com/api/webhooks/123/abc" }

Golden Cross, the 50-day average crossing above the 200-day:

JSON
{ "symbol": "MSFT", "trigger_type": "technical", "metric": "sma50", "condition": "crosses_above", "compare_metric": "sma200", "channel": "webhook", "target": "https://hooks.example.com/apextick" }

Bullish MACD signal-line cross:

JSON
{ "symbol": "AAPL", "trigger_type": "technical", "metric": "macd", "condition": "crosses_above", "compare_metric": "macd_signal", "channel": "telegram", "target": "@my_trading_channel" }

Volume

Fires on relative volume. rvol20 is today's session volume divided by the average daily volume of the last 20 completed sessions, so 2 means 200% of normal volume. Use crosses_above to catch the onset of a surge:

JSON
{ "symbol": "NVDA", "trigger_type": "volume", "metric": "rvol20", "condition": "crosses_above", "threshold": 2, "channel": "webhook", "target": "https://hooks.example.com/apextick" }

compare_metric is not supported for volume triggers.

News and calendar

Accepted, not yet firing

News and calendar triggers are validated, stored and count towards your alert quota today, but they don't fire yet. They will activate automatically once the news and corporate-calendar feeds are live, with no change to your triggers.

News triggers filter headlines by sentiment and category:

JSON
{ "symbol": "TSLA", "trigger_type": "news", "sentiment": "bearish", "category": "regulatory", "channel": "webhook", "target": "https://hooks.example.com/news" }

Calendar triggers notify you ahead of an earnings, dividend or split date:

JSON
{ "symbol": "NVDA", "trigger_type": "calendar", "event": "earnings", "notice_hours_before": 24, "channel": "discord", "target": "https://discord.com/api/webhooks/123/abc" }

Conditions and cooldowns

ConditionKindFires
gt, lt, gte, lteLevelWhile the condition is true, at most once per cooldown_minutes.
crosses_above, crosses_belowCrossingOnce, on the tick where the value crosses the reference. It fires again only after crossing back and crossing again.

The reference is either a fixed threshold or the live value of compare_metric. A trigger must use exactly one of them, and compare_metric must differ from metric.

Only fresh ticks are evaluated: stale or out-of-order data never fires an alert, and evaluation doesn't depend on you having a stream open.

Delivery channels

ChanneltargetDelivered as
emailOne plain email address, e.g. you@example.comEmail
webhookA public https URLSigned JSON POST of the event
telegram@channel_name or a numeric chat idTelegram message
discordA Discord webhook URL (https://discord.com/api/webhooks/…)Discord message
slackA Slack incoming webhook URL (https://hooks.slack.com/services/…)Slack message
restNone (omit target)Recorded only; read it from GET /v1/alerts/events

Webhook-style targets must be public https URLs. Private and internal addresses are rejected, and redirects are not followed. Failed deliveries caused by network errors, 408, 429 or 5xx responses are retried with exponential backoff.

The message text sent to email, Telegram, Discord and Slack looks like this:

Text
ApexTick alert: NVDA rvol20 is 2.34 (crossed above 2) as of 2026-10-16T14:30:05Z

Webhook payload

Webhook deliveries POST the alert event as JSON:

JSON
{
  "alert_id": "6f1c2a9e-3b4d-4c8a-9e21-7d5b0c4e8f13",
  "symbol": "AAPL",
  "trigger_type": "technical",
  "metric": "macd",
  "condition": "crosses_above",
  "threshold": -2.37,
  "compare_metric": "macd_signal",
  "observed_value": -2.13,
  "tick_timestamp": "2026-10-16T14:30:05Z",
  "fired_at": "2026-10-16T14:30:06Z",
  "message": "ApexTick alert: AAPL macd is -2.13 (crossed above macd_signal -2.37) as of 2026-10-16T14:30:05Z"
}

threshold carries the reference value at evaluation time: the fixed threshold, or the value of compare_metric. Each request also carries these headers:

HeaderValue
Content-Typeapplication/json
X-ApexTick-Alert-IdThe alert's id
X-ApexTick-Signaturet=<unix seconds>,v1=<hex signature>

Respond with any 2xx status quickly and do slow work asynchronously.

Verifying webhook signatures

The v1 value is the lowercase hex HMAC-SHA256 of "<t>.<raw body>", keyed with your alert's signing_secret. The timestamp t is part of the signed string, so there is no separate timestamp header. To verify a delivery:

  1. Parse t and v1 from the X-ApexTick-Signature header.
  2. Reject the request if t is more than five minutes from your clock, to block replays.
  3. Compute the HMAC over the raw request body bytes, before any JSON parsing.
  4. Compare signatures in constant time.
Python
import hashlib
import hmac
import time

def verify_apextick(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    ts, sig = parts.get("t"), parts.get("v1")
    if not ts or not sig or not ts.isdigit():
        return False
    if abs(time.time() - int(ts)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

# Flask example
# @app.post("/apextick")
# def apextick_webhook():
#     if not verify_apextick(SECRET, request.headers.get("X-ApexTick-Signature", ""), request.get_data()):
#         abort(401)
#     event = request.get_json()
#     return "", 204

Digest webhooks are signed the same way; see Briefings & Digests.

List alerts

GET/v1/alertsHobbyist+

Returns your active triggers and quota usage. limit is 0 on plans with unlimited alerts. Signing secrets are never listed.

JSON
{
  "data": [
    { "id": "6f1c2a9e-…", "symbol": "NVDA", "trigger_type": "volume", "metric": "rvol20", "condition": "crosses_above", "threshold": 2, "channel": "webhook", "target": "https://hooks.example.com/apextick", "cooldown_minutes": 60, "active": true, "created_at": "2026-10-16T13:05:12Z", "last_fired_at": "2026-10-16T14:30:06Z" }
  ],
  "quota": { "used": 1, "limit": 50 }
}

Delete an alert

DELETE/v1/alerts/{id}Hobbyist+

Deactivates the trigger and frees its quota slot. Returns 204 No Content, or 404 if the alert doesn't exist.

Shell
curl -s -X DELETE "https://api.apextick.com/v1/alerts/6f1c2a9e-3b4d-4c8a-9e21-7d5b0c4e8f13" \
  -H "apikey: $APEXTICK_API_KEY"

Alert events

GET/v1/alerts/eventsHobbyist+

Lists fired alerts, newest first, with their delivery status: delivered, failed, or recorded for the rest channel. Alerts on the rest channel are delivered only here, which makes this endpoint a simple polling inbox. History is kept for 90 days.

Query parameterDescription
alert_idOnly events of this alert.
limitPage size, default 50, maximum 200.
beforeCursor: pass the previous page's next_cursor to get older events.
JSON
{
  "data": [
    {
      "id": 48213,
      "alert_id": "6f1c2a9e-3b4d-4c8a-9e21-7d5b0c4e8f13",
      "symbol": "NVDA",
      "trigger_type": "volume",
      "channel": "webhook",
      "status": "delivered",
      "duration_ms": 142,
      "fired_at": "2026-10-16T14:30:06Z",
      "event": { "alert_id": "6f1c2a9e-…", "symbol": "NVDA", "metric": "rvol20", "observed_value": 2.34, "message": "ApexTick alert: NVDA rvol20 is 2.34 (crossed above 2) as of 2026-10-16T14:30:05Z" }
    }
  ],
  "next_cursor": "48212"
}

Failed deliveries include an error field describing what went wrong.