Skip to content
Cirrus home
Sign in

Triggers

Alerts, stop-loss, targets, entries, ladders and loss limits the server watches tick by tick.

Read the guide first

List v2 triggers #

GET /v2/triggers scope read

Every v2 trigger of the caller, newest first, with its state.

More about this

Ended triggers stay listed for 8 days. ?group= lists only the triggers created with that group.

Also every protection GET /v1/triggers lists that is held as a v1 record (made before 2026-10-28 through /v1/triggers, the order ticket, a signal entry or /v2/triggers; a broker bracket / cover placed with an entry; a /v1/gtt record), as a bracket with plain price levels: route gtt, stop_order, bracket, cover or trigger, a trailing stop-loss as spec.stop_loss.trail.step, a GTT stop-loss and its server-side target as one trigger (its id the GTT's record's). Its id is v1's (not a ULID); created_via is absent. Ended ones (7 days, as on /v1/triggers) show why in state and reason.

Parameters

  • group query · string

    Only the triggers created with this group (exact match).

Errors

HTTP Code When
401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://api.cirrus.trade/v2/triggers" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": [
    {
      "trigger_id": "01M5VVFCDFABPK086FBZD11WZ5",
      "account": "P1",
      "strategy": null,
      "kind": "bracket",
      "legs": [
        {
          "side": "BUY",
          "quantity": 5,
          "product": "MIS",
          "instrument_token": "CT:TEST:RELIANCE"
        }
      ],
      "spec": {
        "entry_px": 2500,
        "target": {
          "when": "ltp >= entry_px * 1.02"
        },
        "stop_loss": {
          "init_sl": "entry_px - 25",
          "trail": {
            "step": "5"
          }
        }
      },
      "valid_until": null,
      "state": "submitted",
      "transition_seq": 0,
      "reason": null,
      "created_at": "2026-10-26T15:48:09.715+05:30",
      "updated_at": "2026-10-26T15:48:09.715+05:30",
      "protection": {
        "stop_loss": {
          "held_by": "server",
          "broker_order_id": null,
          "level": 2475,
          "adjusted_by_server": false,
          "status": "held_by_server",
          "note": null
        },
        "target": {
          "held_by": "server",
          "broker_order_id": null,
          "level": 2550,
          "adjusted_by_server": false,
          "status": "held_by_server",
          "note": null
        },
        "trail": {
          "held_by": "server",
          "broker_order_id": null,
          "level": 2475,
          "adjusted_by_server": false,
          "status": "held_by_server",
          "note": null
        }
      },
      "created_via": "app",
      "route": "trigger"
    }
  ],
  "error": null
}

Create a v2 trigger #

POST /v2/triggers scope triggers

Creates a v2 trigger: an alert, a bracket (entry, target, stop-loss, trailing), a ladder (several targets) or a risk_limit (loss / profit limits on an account, the user or a strategy), with conditions on any tick field.

More about this

The trigger is checked first (its conditions compile, the account is the caller's, every leg's instrument exists; bracket and ladder legs need product), then handed to the trigger engine: 202 with the new trigger's id, made by the server. Read it with GET /v2/triggers/{trigger_id}; state is submitted until the engine reports. A bracket protecting a position already held (entry_px, no entry) may set its stop-loss at or beyond entry_px to lock in profit (long: entry_px <= init_sl < the current price; short mirrored); a waiting bracket's stop stays on the protective side of its entry.

Where the protection lives (route, default auto): a bracket (or ladder) protecting a position already held is always a v2 trigger the server evaluates, whose plain price stop-loss also rests at the broker, broker-native first: a broker GTT for a positional product where the broker has GTTs (one never larger than the position; a broker whose GTT takes one leg holds the stop-loss, the trigger the target), a broker stop-loss order (SL) for an intraday (MIS) position where the broker's stop orders are proven. The broker order is placed, and read back, before the 202, which says route gtt / stop_order and why in route_reason; the trigger watches every part and cancels the broker order before any exit it places. If the broker refuses it, the server holds the protection at once (route trigger, the broker's reason in route_reason); if the broker does not answer, nothing else is placed and the trigger keeps watching (504 broker_outcome_unknown, naming the trigger). route gtt / stop_order ask for that route (refused with 422 route where the trigger cannot take it); route trigger keeps every part with the server.

Parameters

  • Idempotency-Key header · string

    Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of creating another trigger. A broker_outcome_unknown answer is kept like any other: a repeat within 24 hours gets the same 504 back and creates nothing.

Body fields

  • account string required

    Account id (see /v1/accounts).

  • kind string required

    The machine that runs a trigger.

    One of: alert, bracket, ladder, risk_limit

  • spec object required

    Kind-specific rules, conditions written in the CEL subset (ltp <= 120 && bid_qty > ask_qty). Checked before anything is sent: an unknown field or a condition that does not compile is a 422.

  • strategy string | null

    Strategy the trigger belongs to (risk limits at strategy level).

  • legs object[]
  • valid_until string | null

    RFC 3339; the trigger expires then.

  • watch object[]

    Instruments the conditions read but the trigger never trades (at most 3), for indicator conditions: [{"name": "nifty", "symbol": "<instrument token>"}] lets a condition say nifty.rsi(14, '5m') or nifty.ltp. Names are [a-z][a-z0-9_]{0,15}, not a variable or function name. Each counts toward the distinct-instrument limit. Fixed at create (a change keeps them). Refused (422) while indicator conditions are not available.

  • route string

    Where a bracket protecting a held position lives. auto (default) puts a simple protection (one leg already held, a plain price stop-loss and / or target, nothing else) at the broker with the rules of POST /v1/triggers: a broker GTT for a positional product where the broker has GTTs (a one-leg-GTT broker holds the stop-loss, a server-side trigger the target), a broker stop-loss order for an intraday (MIS) position where the broker's stop orders are proven; anything else is a v2 trigger the server evaluates. trigger always makes the v2 trigger (the earlier behaviour). gtt / stop_order ask for that route and are refused (422) where auto would not take it; bracket / cover are refused.

    One of: auto, bracket, cover, gtt, trigger, stop_order

  • group string | null

    A label of your own (1-64 characters), returned on reads and filterable on list (?group=): triggers made as one set (several targets as separate triggers) show as one. Free-form; nothing else reads it.

Errors

HTTP Code When
400 malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

400 invalid_idempotency_key

The Idempotency-Key header is not 1-128 printable characters.

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 account_not_found

No linked broker account with this id for the caller.

409 request_in_progress

A request with this Idempotency-Key is still being processed.

422 invalid_request

The request is well-formed but not valid; field names the input.

422 idempotency_key_reused

This Idempotency-Key was already used with a different request.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

504 broker_outcome_unknown

The broker did not answer in time: it may or may not have applied the change. Check before retrying.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://api.cirrus.trade/v2/triggers" \
  -H "Authorization: token $CIRRUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account": "P1",
    "kind": "bracket",
    "legs": [
      {
        "instrument_token": "CT:TEST:RELIANCE",
        "side": "BUY",
        "quantity": 5,
        "product": "MIS"
      }
    ],
    "spec": {
      "entry_px": 2500,
      "target": {
        "when": "ltp >= entry_px * 1.02"
      },
      "stop_loss": {
        "init_sl": "entry_px - 25",
        "trail": {
          "step": "5"
        }
      }
    }
  }'
Response · 202
{
  "status": "success",
  "data": {
    "trigger_id": "01M65Q5EWB0KC0D6ZG7PT7QVB3",
    "command_id": "01M65Q5EWKP63YC0162BADKHRE",
    "status": "accepted",
    "route": "stop_order",
    "route_reason": "broker stop-loss order holds the stop-loss, a server-side trigger the target"
  },
  "error": null
}

One v2 trigger #

GET /v2/triggers/{trigger_id} scope read

One v2 trigger of the caller.

More about this

Another user's id is not found. A live protection held as a v1 record (see GET /v2/triggers) is read by its v1 id (either half of a GTT stop-loss and its server-side target).

Parameters

  • trigger_id path · string required

    The trigger's id.

Errors

HTTP Code When
401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://api.cirrus.trade/v2/triggers/<trigger_id>" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": {
    "trigger_id": "01M5VVFCDF60NDFJ5SMKG675R2",
    "account": "P1",
    "strategy": null,
    "kind": "bracket",
    "legs": [
      {
        "side": "BUY",
        "quantity": 5,
        "product": "MIS",
        "instrument_token": "CT:TEST:RELIANCE"
      }
    ],
    "spec": {
      "entry_px": 2500,
      "target": {
        "when": "ltp >= entry_px * 1.02"
      },
      "stop_loss": {
        "init_sl": "entry_px - 25",
        "trail": {
          "step": "5"
        }
      }
    },
    "valid_until": null,
    "state": "active",
    "transition_seq": 1,
    "reason": null,
    "created_at": "2026-10-26T15:48:09.715+05:30",
    "updated_at": "2026-10-26T15:48:09.719+05:30",
    "protection": {
      "stop_loss": {
        "held_by": "server",
        "broker_order_id": null,
        "level": 2480,
        "adjusted_by_server": false,
        "status": "held_by_server",
        "note": null
      },
      "target": {
        "held_by": "server",
        "broker_order_id": null,
        "level": 2550,
        "adjusted_by_server": false,
        "status": "held_by_server",
        "note": null
      },
      "trail": {
        "held_by": "server",
        "broker_order_id": null,
        "level": 2475,
        "adjusted_by_server": false,
        "status": "held_by_server",
        "note": null
      }
    },
    "created_via": "app",
    "route": "trigger"
  },
  "error": null
}

Change a v2 trigger #

PATCH /v2/triggers/{trigger_id} scope triggers

Replaces a v2 trigger's spec (and optionally its expiry).

More about this

The new spec is checked like on create; the engine then applies what the trigger's state allows (for example a stop-loss is never loosened, an entry cannot change once active). 202: read the trigger to see the result. Protection the broker holds (route gtt / stop_order) is changed at the broker first and answered once the broker agreed: its spec must stay a plain price stop-loss / target with the same parts, and valid_until cannot be set. A protection held as a v1 record (see GET /v2/triggers) is changed through v1's own code, at the broker first where the broker holds it, with v1's rules (a bracket keeps both parts, a cover a stop-loss only): its spec stays plain price levels with the same parts, its quantity and a trailing stop-loss as shown (422 otherwise); a trailing stop-loss is never set looser than where its trail moved it (409 trigger_busy, field rules: cancel and protect again for a looser stop). valid_until: absent keeps the expiry, null removes it. route: the current route (or auto) changes nothing; another is refused (422 route: cancel and create the trigger again). POST /v2/triggers/{trigger_id}/validate checks a change without making it.

Parameters

  • trigger_id path · string required

    The trigger's id.

  • Idempotency-Key header · string

    Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.

Body fields

  • spec object required

    The whole new spec (same shape as on create).

  • valid_until string | null

    New expiry (RFC 3339); unchanged when absent; null removes it (the trigger then lives until it ends).

  • route string | null

    Where the protection lives (route of the trigger). The route is chosen at create: the current one (or auto) is accepted and changes nothing; any other is refused (422 route) with how to move it.

    One of: auto, bracket, cover, gtt, trigger, stop_order

Errors

HTTP Code When
400 malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

400 invalid_idempotency_key

The Idempotency-Key header is not 1-128 printable characters.

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

404 account_not_found

No linked broker account with this id for the caller.

409 trigger_fired

The trigger already fired; its exit is being handled.

409 broker_held

The protection is held by the broker; change the broker order instead.

409 trigger_busy

The trigger changed while it was being updated; retry.

409 request_in_progress

A request with this Idempotency-Key is still being processed.

422 invalid_request

The request is well-formed but not valid; field names the input.

422 idempotency_key_reused

This Idempotency-Key was already used with a different request.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

502 broker_refused

The broker refused the change.

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

504 broker_outcome_unknown

The broker did not answer in time: it may or may not have applied the change. Check before retrying.

All fields (request and response schema)

Loading fields…

PATCH
curl -X PATCH "https://api.cirrus.trade/v2/triggers/<trigger_id>" \
  -H "Authorization: token $CIRRUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "spec": {
      "entry_px": 2500,
      "target": {
        "when": "ltp >= entry_px * 1.02"
      },
      "stop_loss": {
        "init_sl": "entry_px - 20",
        "trail": {
          "step": "5"
        }
      }
    }
  }'
Response · 202
{
  "status": "success",
  "data": {
    "trigger_id": "01M4GF38AQ39WVJZHH8YWWNNQZ",
    "command_id": "01M4GF38BEE9Y3N9T9M8EHNXKX",
    "status": "accepted"
  },
  "error": null
}

Cancel a v2 trigger #

DELETE /v2/triggers/{trigger_id} scope triggers

Cancels a v2 trigger.

More about this

202: the engine ends it within moments (state cancelled); one that fires first ends as fired instead. Protection the broker holds (route gtt / stop_order) is cancelled at the broker first (a stop order is read back final; one that already went off is exiting and is kept: 502, retry), then the trigger (a protection held as a v1 record, see GET /v2/triggers, is removed through v1's own code with its paired target): broker_cancel says how the broker answered. A refusal never removes it on its own: shown ended at the broker it is removed (cancelled), shown fired it ends as fired (409), otherwise it stays live with state cancel_pending (broker_cancel pending) and is re-checked until the broker shows a final state.

Parameters

  • trigger_id path · string required

    The trigger's id.

  • Idempotency-Key header · string

    Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.

Errors

HTTP Code When
400 invalid_idempotency_key

The Idempotency-Key header is not 1-128 printable characters.

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

409 trigger_fired

The trigger already fired; its exit is being handled.

409 request_in_progress

A request with this Idempotency-Key is still being processed.

422 idempotency_key_reused

This Idempotency-Key was already used with a different request.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

502 broker_refused

The broker refused the change.

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

DELETE
curl -X DELETE "https://api.cirrus.trade/v2/triggers/<trigger_id>" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 202
{
  "status": "success",
  "data": {
    "trigger_id": "01M4GF38B4NDN7T4SDYK85MKNM",
    "command_id": "01M4GF38BK9BQ5Z9NTES6C5R31",
    "status": "accepted"
  },
  "error": null
}

Pause a v2 trigger #

POST /v2/triggers/{trigger_id}/pause scope triggers

Pauses a v2 trigger: the engine stops evaluating it (state paused) until it is resumed.

More about this

A paused trigger does not act on any condition, its stop-loss included; a part the broker holds stays at the broker. 202: the engine applies it within moments; read the trigger to see the result. A protection held as a v1 record (made before 2026-10-28, or a broker bracket / cover placed with an entry) cannot be paused (422).

Parameters

  • trigger_id path · string required

    The trigger's id.

  • Idempotency-Key header · string

    Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.

Errors

HTTP Code When
400 invalid_idempotency_key

The Idempotency-Key header is not 1-128 printable characters.

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

409 trigger_fired

The trigger already fired; its exit is being handled.

409 request_in_progress

A request with this Idempotency-Key is still being processed.

422 invalid_request

The request is well-formed but not valid; field names the input.

422 idempotency_key_reused

This Idempotency-Key was already used with a different request.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://api.cirrus.trade/v2/triggers/<trigger_id>/pause" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 202
{
  "status": "success",
  "data": {
    "trigger_id": "01M4QNBZ1SPDJ4F3K9NA9H72SC",
    "command_id": "01M4QNBZ21PD87BG3EHS08AN78",
    "status": "accepted"
  },
  "error": null
}

Resume a paused v2 trigger #

POST /v2/triggers/{trigger_id}/resume scope triggers

Resumes a paused v2 trigger: the engine evaluates it again from the next tick, in the state it was paused in.

More about this

202: read the trigger to see the result (the engine refuses a resume of a trigger that is not paused). A protection held as a v1 record is never paused (422).

Parameters

  • trigger_id path · string required

    The trigger's id.

  • Idempotency-Key header · string

    Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.

Errors

HTTP Code When
400 invalid_idempotency_key

The Idempotency-Key header is not 1-128 printable characters.

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

409 trigger_fired

The trigger already fired; its exit is being handled.

409 request_in_progress

A request with this Idempotency-Key is still being processed.

422 invalid_request

The request is well-formed but not valid; field names the input.

422 idempotency_key_reused

This Idempotency-Key was already used with a different request.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://api.cirrus.trade/v2/triggers/<trigger_id>/resume" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 202
{
  "status": "success",
  "data": {
    "trigger_id": "01M4QNBZ1SH6BRRRMPJ7RRSVMY",
    "command_id": "01M4QNBZ1Z8KTB78CXT3BMKEBB",
    "status": "accepted"
  },
  "error": null
}

Check a v2 trigger without creating it #

POST /v2/triggers/validate scope triggers

A dry run of POST /v2/triggers with the same body: nothing is sent to the engine, stored or counted against the account's order budget.

More about this

200 with valid and every reason create would refuse it (errors, the same field and message as create's 422), what its conditions read (reads), where it stands against the limits (limits) and, for bracket / ladder, who would hold each part of its protection on this account and product (protection: the broker or the server).

**Expressions** (a strict CEL subset; numbers are doubles). Variables: leg 0's tick fields ltp, bid, ask, bid_qty, ask_qty, volume, oi, atp, open, high, low; prev.<field> (previous tick); legs[i].<field> and legs[i].prev.<field>; entry_px, sl_px, state; account.mtm, user.mtm, strategy.mtm, position.qty; now_ist (minutes since midnight IST, 09:15 = 555). Functions: crosses_above(a, b) / crosses_below(a, b) (against the previous tick; a gap counts), min, max, abs. Operators: || && ! == != < <= > >= + - * / and c ? a : b. Prices (init_sl, trail.step, ladder price) read only entry_px and literals. Limits: 2048 bytes and 32 levels of nesting per condition; the per-trigger limits in limits. Errors name the spec path and byte, e.g. when: unknown variable ltpp (at byte 0). Full reference with examples per kind: docs/api/trigger-expressions.md.

Body fields

  • account string required

    Account id (see /v1/accounts).

  • kind string required

    The machine that runs a trigger.

    One of: alert, bracket, ladder, risk_limit

  • spec object required

    Kind-specific rules, conditions written in the CEL subset (ltp <= 120 && bid_qty > ask_qty). Checked before anything is sent: an unknown field or a condition that does not compile is a 422.

  • strategy string | null

    Strategy the trigger belongs to (risk limits at strategy level).

  • legs object[]
  • valid_until string | null

    RFC 3339; the trigger expires then.

  • watch object[]

    Instruments the conditions read but the trigger never trades (at most 3), for indicator conditions: [{"name": "nifty", "symbol": "<instrument token>"}] lets a condition say nifty.rsi(14, '5m') or nifty.ltp. Names are [a-z][a-z0-9_]{0,15}, not a variable or function name. Each counts toward the distinct-instrument limit. Fixed at create (a change keeps them). Refused (422) while indicator conditions are not available.

  • route string

    Where a bracket protecting a held position lives. auto (default) puts a simple protection (one leg already held, a plain price stop-loss and / or target, nothing else) at the broker with the rules of POST /v1/triggers: a broker GTT for a positional product where the broker has GTTs (a one-leg-GTT broker holds the stop-loss, a server-side trigger the target), a broker stop-loss order for an intraday (MIS) position where the broker's stop orders are proven; anything else is a v2 trigger the server evaluates. trigger always makes the v2 trigger (the earlier behaviour). gtt / stop_order ask for that route and are refused (422) where auto would not take it; bracket / cover are refused.

    One of: auto, bracket, cover, gtt, trigger, stop_order

  • group string | null

    A label of your own (1-64 characters), returned on reads and filterable on list (?group=): triggers made as one set (several targets as separate triggers) show as one. Free-form; nothing else reads it.

Errors

HTTP Code When
400 malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 account_not_found

No linked broker account with this id for the caller.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://api.cirrus.trade/v2/triggers/validate" \
  -H "Authorization: token $CIRRUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account": "P1",
    "kind": "bracket",
    "legs": [
      {
        "instrument_token": "CT:TEST:RELIANCE",
        "side": "BUY",
        "quantity": 5,
        "product": "MIS"
      }
    ],
    "spec": {
      "entry_px": 2500,
      "target": {
        "when": "ltp >= entry_px * 1.02"
      },
      "stop_loss": {
        "init_sl": "entry_px - 25"
      }
    }
  }'
Response · 200
{
  "status": "success",
  "data": {
    "valid": true,
    "errors": [],
    "reads": {
      "symbols": [
        "CT:TEST:RELIANCE"
      ],
      "fields": [
        "ltp"
      ],
      "variables": [
        "entry_px",
        "sl_px"
      ]
    },
    "limits": {
      "ok": true,
      "reason": null,
      "legs": 1,
      "max_legs": 8,
      "expressions": 2,
      "max_expressions": 32,
      "bytes": 310,
      "max_bytes": 16384
    },
    "protection": {
      "parts": {
        "stop_loss": {
          "held_by": "server",
          "level": 2475,
          "adjusted_by_server": false,
          "note": null
        },
        "target": {
          "held_by": "server",
          "level": 2550,
          "adjusted_by_server": false,
          "note": null
        }
      },
      "reason": "the position has not been checked yet"
    },
    "route": "stop_order",
    "route_reason": "broker stop-loss order holds the stop-loss, a server-side trigger the target"
  },
  "error": null
}

Check a change to a v2 trigger without making it #

POST /v2/triggers/{trigger_id}/validate scope triggers

A dry run of PATCH /v2/triggers/{trigger_id} with the same body: nothing is sent to the engine or the broker.

More about this

200 with valid and every reason the change would be refused (errors, the same field and message as the change's 422), what changes in the spec (diff) and the expiry the trigger would have.

Parameters

  • trigger_id path · string required

    The trigger's id.

Body fields

  • spec object required

    The whole new spec (same shape as on create).

  • valid_until string | null

    New expiry (RFC 3339); unchanged when absent; null removes it (the trigger then lives until it ends).

  • route string | null

    Where the protection lives (route of the trigger). The route is chosen at create: the current one (or auto) is accepted and changes nothing; any other is refused (422 route) with how to move it.

    One of: auto, bracket, cover, gtt, trigger, stop_order

Errors

HTTP Code When
400 malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

404 account_not_found

No linked broker account with this id for the caller.

409 trigger_fired

The trigger already fired; its exit is being handled.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://api.cirrus.trade/v2/triggers/<trigger_id>/validate" \
  -H "Authorization: token $CIRRUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "spec": {
      "entry_px": 2500,
      "target": {
        "when": "ltp >= entry_px * 1.02"
      },
      "stop_loss": {
        "init_sl": "entry_px - 20"
      }
    },
    "valid_until": null
  }'
Response · 200
{
  "status": "success",
  "data": {
    "valid": true,
    "errors": [],
    "diff": [
      {
        "path": "stop_loss.init_sl",
        "from": "entry_px - 25",
        "to": "entry_px - 20"
      }
    ],
    "valid_until": null
  },
  "error": null
}

What v2 triggers can do #

GET /v2/triggers/capabilities scope read

The kinds and their spec fields, the expression language (variables, functions, operators and its size limits), the limits this deployment runs with and, with ?account=, what that account's broker can hold (order types, and each protection fact verified or unverified) and whether broker protection is on.

More about this

See docs/api/trigger-expressions.md.

Parameters

  • account query · string

    One of the caller's accounts: adds what its broker can hold.

Errors

HTTP Code When
401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 account_not_found

No linked broker account with this id for the caller.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

GET
curl "https://api.cirrus.trade/v2/triggers/capabilities" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200 · lists shortened
{
  "status": "success",
  "data": {
    "kinds": [
      {
        "kind": "alert",
        "description": "Notifies once `when` holds, then ends (`fired`).",
        "legs": "one or more",
        "spec_fields": [
          {
            "name": "when",
            "type": "condition",
            "required": true,
            "description": "Tick fields of every leg, risk variables, `now_ist`."
          },
          {
            "name": "on",
            "type": "string",
            "required": false,
            "description": "When the condition next to it is checked: `close` (default when it reads an indicator: at each candle close, with closed values) or `tick` (every price tick, with the forming candle)."
          }
        ]
      },
      {
        "kind": "bracket",
        "description": "Entry (a condition, or a known `entry_px`), target, stop-loss and trailing stop on one position.",
        "legs": "one",
        "spec_fields": [
          {
            "name": "entry.when",
            "type": "condition",
            "required": false,
            "description": "Waits (`pending`) until it holds; `entry_px` becomes that tick's LTP. Exclusive with `entry_px`."
          },
          {
            "name": "entry.on",
            "type": "string",
            "required": false,
            "description": "When the condition next to it is checked: `close` (default when it reads an indicator: at each candle close, with closed values) or `tick` (every price tick, with the forming candle)."
          }
        ]
      }
    ],
    "expressions": {
      "variables": [
        {
          "name": "ltp",
          "description": "Leg 0's tick field; `prev.<field>` the previous tick's, `legs[i].<field>` leg i's."
        },
        {
          "name": "bid",
          "description": "Leg 0's tick field; `prev.<field>` the previous tick's, `legs[i].<field>` leg i's."
        }
      ],
      "functions": [
        {
          "name": "crosses_above",
          "description": "crosses_above(a, b[, band]): a was at or below b on the previous tick and is above it now. Over an indicator, or with a dead band `band` (a number 0 or more), it fires once per real crossing: values inside b ± band keep the last side, and the first side seen only arms. Tick mode on a bracket defaults the band to 1 point (RSI, Stochastic) or 0.05 % of the level (prices)."
        },
        {
          "name": "crosses_below",
          "description": "crosses_below(a, b[, band]): a was at or above b on the previous tick and is below it now; `band` as for crosses_above."
        }
      ],
      "operators": [
        "||",
        "&&"
      ],
      "max_source_bytes": 2048,
      "max_depth": 32,
      "max_nodes": 256
    },
    "limits": {
      "max_triggers": 500,
      "max_legs": 8,
      "max_expressions": 32,
      "max_symbols": 100,
      "max_trigger_bytes": 16384,
      "max_rungs": 20
    },
    "entries_enabled": false,
    "risk_targets_enabled": false,
    "products": [
      {
        "segment": "equity",
        "exchanges": [
          "NSE",
          "BSE"
        ],
        "products": [
          "MIS",
          "DELIVERY"
        ]
      },
      {
        "segment": "derivatives",
        "exchanges": [
          "NFO",
          "BFO"
        ],
        "products": [
          "MIS",
          "CARRYFORWARD"
        ]
      }
    ],
    "account": null
  },
  "error": null
}

A v2 trigger's journey #

GET /v2/triggers/{trigger_id}/journey scope read

Everything that happened to one of the caller's v2 triggers, newest first: what was asked (api: created, modified with the spec diff, paused, resumed, cancelled, with who asked), what the engine did and why (engine: each transition with the condition that decided it and the values it read, e.g.

More about this

ltp=2479.5, sl_px=2480 in ltp <= sl_px; notes such as an input not known), commands it refused (result), exit orders (exit) and the broker protection (protection). Kept for 90 days by default, also after the trigger ended. Another user's trigger is not found.

Parameters

  • trigger_id path · string required

    The trigger's id.

  • limit query · integer

    Entries per page, 1 to 200 (default 50).

  • cursor query · string

    next_cursor of the previous page.

  • source query · string

    Only entries from api, engine, result, exit or protection.

Errors

HTTP Code When
400 invalid_query

The query string does not fit the route's parameters.

401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://api.cirrus.trade/v2/triggers/<trigger_id>/journey" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": {
    "entries": [
      {
        "entry_id": "evt:01M4QRFDBAQC442FN7H0ATGR3A:1",
        "at": "2026-10-12T15:23:05.396+05:30",
        "seq": 1,
        "source": "engine",
        "kind": "transition",
        "summary": "active -> active",
        "actor": null,
        "details": {
          "from": "active",
          "to": "active",
          "actions": [
            {
              "type": "move_sl",
              "legs": [
                0
              ],
              "price": 2480
            }
          ],
          "tick": {
            "symbol": "CT:TEST:RELIANCE",
            "ltp": 2551
          },
          "reason": null,
          "explain": null
        }
      },
      {
        "entry_id": "api:01M4QRFDBBMKYVDBYQ05TTGVX2",
        "at": "2026-10-12T15:23:05.386+05:30",
        "seq": 0,
        "source": "api",
        "kind": "created",
        "summary": "created in the app (command 01M4QRFDBBMKYVDBYQ05TTGVX2)",
        "actor": {
          "user": "user-01M4QRFD9EACA9SER6K3EHY0BQ",
          "credential": "app"
        },
        "details": {
          "kind": "bracket",
          "legs": [
            {
              "symbol": "CT:TEST:RELIANCE",
              "side": "BUY",
              "qty": 5,
              "product": "MIS"
            }
          ],
          "spec": {
            "entry_px": 2500,
            "target": {
              "when": "ltp >= entry_px * 1.02"
            },
            "stop_loss": {
              "init_sl": "entry_px - 25"
            }
          },
          "valid_until": null
        }
      }
    ],
    "next_cursor": null
  },
  "error": null
}

A v2 trigger's indicator values now #

GET /v2/triggers/{trigger_id}/indicators scope read

Each indicator a trigger's conditions read, as the engine holds it now: the last closed candle's value, the forming candle's, its warm-up status, how far it is from the condition's level and a tick-mode hold in progress.

More about this

Read from the engine's memory (nothing is computed or stored for it); poll every 2 s while a page shows it. status unavailable when the engine did not answer within 500 ms. At most one request per second per trigger.

Parameters

  • trigger_id path · string required

    The trigger's id.

Errors

HTTP Code When
401 unauthorized

No credential, or the credential is invalid, expired or revoked.

403 forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404 trigger_not_found

No trigger with this id for the caller.

429 rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503 service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://api.cirrus.trade/v2/triggers/<trigger_id>/indicators" \
  -H "Authorization: token $CIRRUS_API_KEY"