Skip to content
Cirrus home
Sign in

Changelog

Every change you can see, newest first. Additive changes never break a client that ignores unknown fields; breaking ones are announced before they ship.

  1. Additive

    Automated strategies: backtests (flag off)

    Behind OMS_AUTO_BACKTEST (and the automated-strategies allowlist); with the flag off every route answers 403 and nothing users see changes. None of this has shipped.

    • New POST /v2/backtests (BacktestInput: strategy_id, version?, from, to, capital?, costs? {charges india | zero, slippage_bps}) → 202 BacktestView; GET /v2/backtests, GET /v2/backtests/{backtest_id} (status queued | running | done | failed | cancelled, or unknown for a status this server does not know yet, progress, summary, daily equity, labels, sources, counts), GET /v2/backtests/{backtest_id}/trades and .../cycles (pages: offset, limit ≤ 200), POST /v2/backtests/{backtest_id}/cancel.
    • A result on modelled option prices says so in labels.text ("Estimated: option prices are modelled from NIFTY and India VIX, not real trades") and on every trade (estimated: true).
    • New error codes backtest_not_found (404), backtest_limit_reached (409: 3 queued or running, 50 a day), backtest_finished (409); 429 rate_limited above 10 submissions a minute. Results are kept 30 days.
  2. Additive

    Automated strategies: adjustments (flags off)

    Behind OMS_AUTO_STRATEGIES (+ OMS_AUTO_STRATEGIES_USERS). Nothing users see changes with the flags off, and none of this has shipped.

    • Strategy documents: close_leg@1, reduce@1, roll_strike@1, add_hedge@1 and roll_expiry@1 are now preview (runnable); new widen@1 (strikes, 1-20: every sold option and its hedge move that many strikes further out). Every adjuster gains an optional trigger (on: loss / profit {rupees}, loss_pct / profit_pct {pct of the entry premium}, elapsed {minutes}, premium_ratio {leg, ratio}, condition {when}, expiry {dte_at_most, at}; delta_band {band} is phase C, saved but not deployable), and when becomes optional: give one of the two (validation says so). Normalized documents write "trigger": null on adjustments that use when. Validation refuses closing a hedge a sold leg needs, a hedge added to a leg that is not a sold option, and legs that don't exist. Summaries say the trigger in words.
    • Engine: an adjustment fires when its trigger turns true (not again while it stays true), at most max_per_cycle per rule and 10 per trade, at least a minute apart, never while paused, stopped, blocked, tripped, switched off, with stale prices, an exit in flight or the order limit reached. Closes go first (a sold leg is bought back before its replacement is sold), a hedge is replaced first, opens go hedge-first, a roll never opens more than it closed, and no step leaves more sold options unhedged or more sold than when it started. Stops, targets and exits keep precedence and never wait for an adjustment.
    • Journey step codes (event auto.code, already "other values may appear"): adjust_sent, adjust_step, adjusted, adjust_skipped, adjust_halted, adjust_unknown (pages like entry_unknown). Exit reason adjustment when an adjustment closed the last leg.
    • Stored / NATS (readers first: these pods read them before any writer ships): the unit plan's adjustments rules; the snapshot's cycle.adjust, leg base / hedge_for / expiry / replaces; StrategyEnter legs' optional anchor (the held contract a new leg is placed against); ExitReason::adjustment; adjustment step events reuse strategy_enter / strategy_exit.
  3. Additive

    Automated strategies: sizing by risk or capital, stagger, sharing (flags off)

    Behind OMS_AUTO_STRATEGIES (+ OMS_AUTO_STRATEGIES_USERS); sharing also behind OMS_AUTO_STRATEGIES_SHARING (default off). Nothing users see changes with the flags off, and none of these operations has shipped.

    • Strategy documents: risk@1 and capital_pct@1 sizing are now preview (runnable). risk@1 needs a combined stop or a defined-risk structure (every sold option covered, one expiry), else validation says "Set a combined stop or add hedges to size by risk." execution.order:

    stagger (with stagger_ms) is no longer a blocker. Summaries say how each sizes and the gap between legs.

    • Execution: risk_per_trade lots = floor(risk × multiplier / the most one unit can lose: the contracts' defined risk at expiry, else the combined stop), capped per leg keeping the ratio; capital_pct lots = floor( available margin × pct × multiplier / the broker's margin for one unit), worked out before the entry is stored (write-ahead). Unknown funds or a budget below one lot refuse the entry with the reason (other / zero_lots). stagger sends one leg at a time, hedges first, each after the one before is final plus stagger_ms; a main takes its hedge's filled share and is not sent when the hedge filled nothing; every leg is still claimed up front and never resent.
    • New PUT / DELETE /v2/strategies/{strategy_id}/sharing (SharingInput: visibility private | users | link, shared_with, link create | revoke; answer SharingView, the link token only in the answer that made it). New error codes no_saved_version (409) and shared_not_found (404).
    • New GET /v2/shared, GET /v2/shared/strategies/{strategy_id} and GET /v2/shared/{link_token} (SharedStrategyView: the newest saved version's document and summary, the owner's short name, read_only; never the draft, accounts, deployments, orders, P&L, journeys or webhooks).
    • POST /v2/strategies/import: from.link_token (new, optional) forks the version a share link shows; from.strategy_id (now optional, one of the two) also forks a strategy shared with the caller by name (saved versions only). Forks are the caller's own private drafts and never deploy.
    • GET /v2/strategies/{strategy_id}: optional update_available (UpdateAvailable: version, forked_version, message) on a fork whose source, still visible to the caller, has a newer saved version. visibility may now be users or link (values already documented).
    • Stored / NATS (readers first: these pods read them before any writer ships): the unit plan's sizing.mode gains risk_per_trade and capital_pct (with risk_rupees, capital_pct), execution.order gains stagger (with stagger_ms); entry jobs carry exec.order:

    stagger. Mongo auto_strategies now writes visibility, shared_with and link (fields existed since phase A) and has two new indexes.

  4. Additive

    Signals on algo mode (flag off)

    Behind OMS_SIGNALS_ALGO_MODE (and the automated-strategies allowlist). With the flag off, a signal URL never switched behaves exactly as before (its mode is looked up, and there is none). Fail closed: a URL left in algo mode while algo mode is off (or whose strategy is stopped or gone, or whose mode is unreadable) is never placed directly: the alert is answered 200 and ignored ("algo mode is paused for this URL; switch the URL back to direct to trade it directly"), recorded in the delivery and activity logs, and the user gets a notice (anomaly signal_algo_paused, "Strategy entry refused" event). Listing and switching back to direct work whatever the flags; switching to algo needs them (403 otherwise).

    • New GET /v2/signal-modes (SignalModeView list: each signal URL with its strategy's mode direct | algo, deployment_id, switched_at, can_use_algo) and PUT /v2/signal-modes/{endpoint_id} (SignalModeInput: mode, deployment_id), app session only. New error code not_flat (409): switching needs no position held by the Signals strategy and no open trade on the deployments involved.
    • POST /v1/hooks/{kind}/{secret}: unchanged URLs, payloads, checks and replies. For a TradingView or Chartink strategy switched to algo, an alert is sent to the bound deployment as a signal (TradingView entry -> enter, exit -> exit; Chartink -> enter) instead of placing orders; its delivery log says "... alert sent to automated strategy “X”" and the activity log records it. Kuberhunt URLs stay direct.
    • Strategy documents: webhook@1 as an entry or exit source is now preview (runnable); a deploy using it is refused (not_deployable) while OMS_SIGNALS_ALGO_MODE is off.
    • Stored: new Mongo collection signal_modes (new record kind, reader test); engine commands now carry CommandOp::Signal with source: webhook (readers shipped in phase A).
  5. Additive

    Groww funds and trades read as the broker reports them

    No field, type or enum changes; Groww accounts' values only. GET /v1/portfolio/margins: a Groww account's available is Groww's equity_margin_details.cnc_balance_available (was clear_cash, which Groww reports as 0, so funds read 0), opening_balance is available plus margin in use (a delivery sell's credit no longer makes it negative), and utilised stays in 0-100. GET /v1/portfolio/trades and trade webhooks: a Groww fill read before Groww lists its trade rows is read again on the next refresh instead of staying missing for the day. Found by the first live Groww conformance runs, which also recorded CNC (DELIVERY) place / modify / cancel and a CNC sell refused for lack of holdings. Such a refusal (Groww's HTTP 200 with order_status FAILED) is now the order's rejection at once, with Groww's remark as the message, instead of an unconfirmed placement resolved REJECTED on the next order-book poll.

  6. Breaking

    Journey paging parameters documented in the query

    GET /v2/deployments/{deployment_id}/journey: cursor and limit were documented as path parameters; they are, and always were, query parameters (?cursor=&limit=). Only the description changes; the server behaves as before. The diff tool sees two path parameters removed. Notified: 2026-10-19, app-cirrus #296 (the only client) reported it and asked for this fix; the operation has not shipped (PR #130 unmerged, flag off).

  7. Additive

    Automated strategies: analytics, P&L, templates, labels (flags off)

    Behind OMS_AUTO_STRATEGIES (+ OMS_AUTO_STRATEGIES_USERS); nothing users see changes with the flag off, and none of these operations has shipped.

    • POST /v2/strategies/dry-run: optional analytics (StrategyAnalytics, as GET /v1/strategies/{name}?scenario=1: payoff at expiry and today, breakevens, max profit / loss with the unlimited flags, probability of profit, per-leg IV and greeks, spot × IV grid) of the contracts it would pick, margin_estimate (AutoMarginEstimate: amount, method defined_risk | contract_value, note), and analytics_note when there are none.
    • GET /v2/strategies items, GET /v2/deployments/{id} and GET /v2/strategies/{id}/deployments: optional pnl (AutoPnl: trading_day, realized, unrealized, total, ltp_stale) from the strategy ledgers by the deployments' strategy tags.
    • GET /v2/deployments/{id} and GET /v2/strategies/{id}/deployments: optional safety_pauses (AutoSafetyPause: account, detector, evidence, at, trips_today, locked_until).
    • GET /v2/deployments/{id}/live: unit gains optional greeks (net, incl. net delta) and analytics_note; each leg optional iv_pct and greeks.
    • New GET /v2/strategies/templates (AutoStrategyTemplate): ready strategy documents with name, goal, description, category, risk badge, components, indicators, broker needs, summary, deployable / blockers.
    • GET /v2/strategies/capabilities: underlying_details (segment, lot size, strike step, expiry kinds, next expiries, spot), labels ([{value, label}] for every enum value), enums.hedge_with.
    • GET /v2/strategies/components and the published strategy JSON Schema (/v2/strategies/schema/1.json): x-labels beside every enum.
  8. Additive

    Automated strategies phase A wired end to end (flags off)

    Everything below is behind OMS_AUTO_STRATEGIES (+ OMS_AUTO_STRATEGIES_USERS; 403 otherwise) or OMS_TRIGGERS_V2_INDICATORS; with the flags off nothing users see changes. None of these operations or records has shipped yet.

    • New GET /v2/deployments/{deployment_id}/journey: every step on every account, newest first, paginated (cursor, limit 1-200; 422 invalid_request otherwise): at, account, day, cycle, kind, what_happened, what_we_did, what_you_should_do, broker_message (schemas AutoJourneyPage, AutoJourneyEntry).
    • POST /v2/deployments/{id}/resume takes an optional body {"acknowledged": bool}; after a safety pause (circuit breaker) it is required, else 422 acknowledgement_required with what the breaker saw.
    • GET /v2/deployments/{id}/live: each unit gains an optional typed unit (AutoUnitNumbers: state, cycle, open P&L, day P&L, trades today, floor, open legs, condition values with threshold and distance, P&L series, breaker detector / evidence / trips, blocked) from the engine; cycle details' journey entries are AutoJourneyEntry objects.
    • Deployments gain optional strategy_name (stored on new deployments).
    • POST /v2/strategies/dry-run gains an optional at (window and days as at that time); entries are picked from the live chain by the leg previewer (would_enter with legs and net premium, or would_skip with the reason); a condition entry is would_wait with what it would pick now.
    • GET /v2/strategies/capabilities gains indicator_functions while indicator conditions are on (the engine's registry).
    • Strategy document format v1: execution.retry_next_signal is optional (null / absent: a refused entry is tried again after a short wait; 0: no more trades that day). Examples updated in place (only that field and the compiled hashes it changes).
    • Validation refuses exit conditions with different check / hold settings and API sources with different signal age limits (they used to be merged silently); strategy conditions are checked by the engine's own compiler (watched names u.ltp; indicators, candles and crossings while indicator conditions are on).
    • Notifications: new event types strategy_armed, strategy_refused, strategy_safety_pause, strategy_kill_switch, strategy_error; strategy_entered and strategy_exited are now sent (examples of list_notification_events and the preference defaults gain them). Strategy steps follow the user's preferences, narrowed to the strategy's own notify list; urgent ones go to every channel. New records (readers first: the readers ship in this release, the writers are switched on with the flags): strategy_step on core NATS oms.strategy.{user}.{deployment_id} (added to the OMS_EVENTS stream and the user-notify consumer's subjects; a consumer created by an earlier build keeps its old subjects until recreated); auto_cycles rows written by the strategy projector (with last_seq); the engine's live-values answer gains an optional auto block; auto_strategy units carry the plan's watched symbols in Trigger.watch and their indicator-condition state in Snapshot.cond.
  9. Additive

    Automated strategies wired to the OMS executor

    No REST request, response, webhook or /v1/stream message changes. New records, read and written only by this code: the resolved entry job oms:auto:{ENV}:entry:{event_id} and its day index oms:auto:{ENV}:entries:{day} (Redis), the expiry lists oms:auto:{ENV}:expiries (Redis, the shape W2's runtime already reads), and the takeover request RebuildRequest on core NATS {ENV}.trg.auto.rebuild (readers first: the api reader ships with or before the runtime writer). The worker now writes the fill / entry_outcome commands W2 added readers for, behind OMS_AUTO_STRATEGIES (default off). The fill command gains an optional sized_qty (units the worker asked the broker for on an entry leg; the engine takes it as the cycle's plan when sizing scaled the lots down). Readers first: the engine that reads it ships before the worker that writes it is switched on; an older engine ignores it. The auto_strategy plan gains two optional options: execution.retry_next_signal (refused entries tried again on new signals at most this many times per cycle, then done for the day; absent = as before) and reentry.same_direction_only (default false). The plan is strict, so readers first: this engine ships before W1's compiler writes them.

  10. Additive

    Watched instruments on v2 triggers (`watch`)

    POST /v2/triggers and POST /v2/triggers/validate accept an optional watch: up to 3 {name, symbol} (instrument token) the conditions read but the trigger never trades, so a condition can say nifty.rsi(14, '5m') or nifty.ltp. Names are [a-z][a-z0-9_]{0,15} and not a variable or function name; each instrument counts toward the distinct-instrument limit; watch is fixed at create (PATCH keeps it). New 422s: watch "indicator conditions are not available yet" while indicator conditions are switched off; watch "at most 3 watched instruments…"; watch[i].symbol "instrument … not found or expired"; spec "watch[i].name: name watched instruments with lowercase letters, like nifty". validate's reads.symbols lists the watched instruments a condition reads and reads.fields their fields (nifty.ltp). The malformed_json message for an unknown request field now lists watch among the expected fields: examples create_trigger_v2 / client_id and validate_trigger_v2 / client_id re-recorded (only that message changed).

  11. Additive

    Automated strategies API, strategy document format (flag off)

    New operations under tag automated_strategies, all off by default (OMS_AUTO_STRATEGIES + OMS_AUTO_STRATEGIES_USERS; 403 otherwise): /v2/strategies (create, list, get, edit draft, archive, versions, clone, export, import, validate, dry-run, components, capabilities, stop-all), /v2/strategies/schema/{n}.json (the published JSON Schema, public), /v2/strategies/{id}/deployments (deploy, list) and /v2/deployments/{id} (get, pause, resume, stop, exit-now, go-live, signals, live, cycles, cycle). New pinned contract: the strategy document format v1, docs/api/strategy.schema.v1.json (additive within v1). New error codes, used only by these operations: strategy_not_found, version_not_found, deployment_not_found, schema_not_found, strategy_busy, strategy_in_use, deployment_finished, deployment_limit_reached, invalid_document, not_deployable, acknowledgement_required, live_not_enabled. New schemas Auto*. API key scopes strategies, strategies_live, strategies_signal are read and enforced (readers first) but cannot be granted yet: key and partner-app creation refuse them, and the Scope schema still lists only read, orders, triggers. Examples recorded for the new operations.

  12. Additive

    Automated strategy units in the trigger engine (readers first)

    No REST request, response, webhook or /v1/stream message changes. Stored and NATS records gain readers for the new auto_strategy engine kind (spec 2026-10-18-automated-strategies-design.md §4.2, stage S0): trigger kind auto_strategy; actions strategy_enter / strategy_exit; command ops fill, entry_outcome, exit_now, stop, signal; events gain an optional auto block (cycle, day, phases, code, detail); snapshots gain an optional auto unit state; notes of kind auto_*. enums in wire_enums.json (TriggerKind, TriggerActionType, Auto*). Nothing writes them yet (OMS_AUTO_STRATEGIES off; no API creates units). GET /v2/triggers and /v1/strategies never list an auto_strategy unit, /v2/triggers/{id} answers 404 for one, and its updates are not sent on the triggers stream, so no new kind value reaches clients. strategy_exit legs gain optional lot_size and hedge_for, the action an optional chase; a fill may name its cumulative quantity qty (alias of filled_qty).

  13. Additive

    Indicator conditions in v2 triggers (switched off)

    Behind OMS_TRIGGERS_V2_INDICATORS (off: nothing users see changes). New GET /v2/triggers/{trigger_id}/indicators: each indicator value a trigger's conditions read, from the engine's memory (status ok, or unavailable when the engine does not answer within 500 ms; 429 above one request per second per trigger). POST /v2/triggers/validate gains an optional conditions (plain-English reading of conditions that use indicators, what they read and the history they need; absent otherwise). GET /v2/triggers/capabilities gains an optional indicators (functions with parameter ranges, defaults and argument schemas, presets, timeframes, exchanges, limits; absent while switched off). POST /v2/triggers, PATCH /v2/triggers/{trigger_id} and validate: a spec using indicator functions, on, hold_s or repeat is 422 invalid_request (field spec) "indicator conditions are not available yet" while switched off; once on, new 422 messages: no VWAP or candle volume on an index, repeat on an alert only (spec.repeat), NSE and BSE instruments only. No recorded example changed by this entry; the new operation's responses are listed in coverage-gaps.json until recorded against a running engine.

  14. Additive

    Indicator conditions, engine records (readers first)

    Trigger engine v2 records other pods read (no REST change yet; the api keeps refusing the new fields until a later release). A v2 trigger may carry watch ([{name, symbol}], instruments read but never traded), and its alert / bracket conditions on (close | tick), hold_s and, on alerts, repeat {cooldown_bars, max_per_day}; snapshots in {ENV}_TRG_STATE gain an optional cond (crossing sides, holds, repeat counters); a repeating alert's events go armed to armed; notes gain kinds stale_prices, data_gap, repeat, repeat_cap, indicator_warming, indicator_ready, indicator_fallback; new KV {ENV}_TRG_IND holds series snapshots (stored kind trg_ind_series, status warming | ready | fallback). Every new field is optional and absent on older records; the engine that reads them ships before anything writes them (OMS_TRIGGERS_V2_INDICATORS, off by default, refuses them at create while off).

  15. Additive

    User notifications (Telegram, app, webhooks)

    New notifications operations (app session only; 503 notifications_disabled unless the server turns them on): GET /v1/notifications (the app inbox, 30 days, limit / before), GET /v1/notifications/events (every event type with its group, default, mandatory and available), GET /v1/notifications/channels (Telegram not_linked / linked / broken with broken_reason, the app, the user's webhooks, and prompts for the app to show: connect_telegram, telegram_broken, webhook_failing), GET / PUT /v1/notifications/preferences (per channel telegram, app_push, webhook: on / off, each event on / off, scope all or selected strategies and triggers, and for webhook the webhook_ids), POST / DELETE /v1/notifications/telegram/link (a one-time https://t.me/<bot>?start=<token> link; unlink) and POST /v1/notifications/telegram/test. New error codes notifications_disabled (503) and telegram_not_linked (404). New schemas Notification and friends; examples for every new operation. Every notice says what_happened, and for failures what_we_did and what_you_should_do. /v1/stream: a new opt-in topic notifications and message notification (data: a Notification), sent only to connections that subscribe to it, and only to the signed-in app session (API keys and partner tokens are refused the topic, as they are GET /v1/notifications), so no existing client sees a new message type. Outbound webhooks: a new event notification (component WebhookNotification), sent only to the webhooks a user picks in the webhook channel of their notification preferences; it is not subscribable through events, so no existing receiver gets it. Its deliveries list type notification in GET /v1/postbacks/{id}/deliveries for those webhooks only. No existing request, response, message or example changed.

  16. Additive

    Probability of profit in strategy analytics

    GET /v1/strategies and GET /v1/strategies/{name}: analytics gains an optional probability_of_profit (0 to 1, 4 decimals): the risk-neutral chance that the open positions' P&L at expiry is above zero, with the underlying lognormal from spot at rate (dividend yield 0), from ferro_ta_core Strategy::probability_of_profit. The model takes one volatility: the option legs' IVs averaged, each weighted by the leg's |vega| (a plain mean when every vega is ~0), to the options' common expiry. Absent, with a sentence in note, when an option leg has no IV, the options expire on different dates, or there is no option leg. Examples get_strategies / options_summary and get_strategy / options_analytics, options_scenario gain the field (nothing else in them changed). Golden comparison treats it as time-dependent, like IV.

  17. Additive

    Lock-in-profit stops on held v2 brackets

    POST /v2/triggers and POST /v2/triggers/validate: a bracket protecting a position already held (entry_px, no entry) may now set stop_loss.init_sl at or beyond entry_px to lock in profit (long: entry_px <= init_sl < the current price; short mirrored). Before, any such stop was refused as "not on the protective side of the entry". A stop at or past the current price (it would fire at once), or one with no current price to check against, is 422 invalid_request with field spec.stop_loss.init_sl and a plain-English message (the dry run lists the same error). PATCH /v2/triggers/{trigger_id} on an active bracket may likewise move the stop into profit (only tighter, never past the market, as before). A bracket waiting for entry keeps its stop on the protective side. The engine checks the price again at create and refuses the trigger with the same words (last_result) when the market moved through the stop meanwhile. No example changed.

  18. Additive

    Where a v2 trigger came from (`created_via`)

    GET /v2/triggers (each row) and GET /v2/triggers/{trigger_id} gain an optional created_via, recorded when the trigger is made: app (a signed-in app session), api (an API key or a partner app's token) or order_ticket (made by the server to protect a POST /v1/orders fill, the order ticket's stop-loss / target). Absent for triggers made before this change. The app's Protect flow uses the same POST /v2/triggers as any app-made trigger and shows as app. Examples list_triggers_v2 / success and get_trigger_v2 / success, entered re-recorded.

  19. Additive

    Options analytics on strategies (`analytics`)

    GET /v1/strategies and GET /v1/strategies/{name}: a strategy whose open positions are all options / futures on one underlying gains an optional analytics object (absent otherwise): underlying, spot (the index or stock price used), model (black_scholes, on the underlying's price, dividend yield 0), rate (yearly, default 0.065), net greeks (delta, gamma per point of the underlying, theta_per_day per calendar day, vega_per_vol_point per 1 point of IV; null when an option leg has no IV), exact breakevens of the expiry payoff, max_profit / max_loss (null with max_profit_unlimited / max_loss_unlimited true when unbounded) and a plain-English note for anything left out (no last price, no underlying price, an IV that does not solve, mixed underlyings). GET /v1/strategies/{name} adds legs (per leg: iv_pct, days_to_expiry, the leg's greeks) and payoff (spots ±10% around the underlying, 61 points; at_expiry; today at the legs' IVs). New optional query scenario on GET /v1/strategies/{name} (0 or 1, other values 422 field scenario): 1 adds analytics.scenario (spot_shifts_pct −5..+5, iv_shifts −5/0/+5 points, today_pnl[iv][spot]). Time to expiry runs to 15:30 IST of the expiry date on a 365-day calendar year. Computed per request from the ledger and our prices; read-only. New examples get_strategies / options_summary, get_strategy / options_analytics, options_scenario, invalid_scenario, unknown_parameter. Golden comparison treats time-dependent numbers (IV, greeks, today, today_pnl, days_to_expiry) as placeholders.

  20. Additive

    v2 entries have their own allowlist (`entries_enabled`)

    GET /v2/triggers/capabilities gains entries_enabled (boolean, per user): whether a bracket with entry places its entry order for the caller. With automatic entries switched on for the deployment (OMS_TRIGGERS_V2_ENTER), only users in its own allowlist (OMS_TRIGGERS_V2_ENTER_USERS) may make such a bracket: for others POST /v2/triggers answers 422 invalid_request (field spec.entry) and POST /v2/triggers/validate lists the same error. With entries off (today) nothing changes. Example get_trigger_capabilities_v2 updated (the new field only).