POST /run

Free evaluate endpoints: single-script /run and multi-script /run/batch over shared OHLCV.

This page

POST /run and /run/batch

Abstract

/run is the primary free evaluate entry: accept Pine source + bar list, optionally wire request.* data sources, execute via package pynescript.runtime.Runtime (Pro API uses backend.runtime re-export), and return plots/series/events/drawings/alerts. /run/batch runs up to eight scripts on the same OHLCV (AXIS multi-indicator), isolating per-script errors so one failure does not discard siblings. Optional free-tier guards (FREE_TIER_LIMITS, default off) enforce bar/script caps, rate limit, concurrency, and chart/mock-only data (backend/middleware/free_limits.py). Optional L2 webhooks POST last-bar (default) alert() / alertcondition() firings to webhook_url or env ALERT_WEBHOOK_URL (SSRF-safe).

Conceptual model

Diagram

Rendering…

Interface surface

POST /run

Request (RUN_SCHEMA)

FieldTypeRequiredDefaultNotes
scriptstringyesPine source
datalistyesOHLCV bar objects
symbolstringno"CHART"Syminfo / request wiring
data_sourcestringno""mock / ccxt / yahoo / …
data_optionsobjectno{}Provider options
modestringno"auto""interpret" | "compile" | "auto"
inputsobjectno{}Pine input.* overrides by title
profilerboolnofalsePer-line timing on interpret
webhook_urlstringno""L2 alert webhook (overrides ALERT_WEBHOOK_URL)
forward_alertsboolnotrueWhen false, skip outbound webhook
alert_last_barboolnotrueWebhook only last OHLCV bar firings
alert_batchboolnotrueOne batch POST vs per-alert
librarieslistno[][{namespace, name, version, source}] — AXIS git-publish emulator (max 32)
timeout_secondsnumbernoomit / nullOptional interpret wall-clock budget. Passed to Runtime.run only when set and > 0. Omit = no timeout.

Query ?mode= is accepted when the body omits mode.

Unknown fields → UNKNOWN_FIELDS 400. Empty script/data still checked after schema with NO_SCRIPT / NO_DATA. import and any request. token are compile-ineligible; mode=auto falls back to interpret and keeps libraries on that path.

Bar shape (runtime expectation):

{ "time": 0, "open": 1, "high": 1, "low": 1, "close": 1, "volume": 1 }

Optional bid / ask per bar update runtime bid/ask state.

Success response

{
  "status": "success",
  "plots": [],
  "series": {},
  "plot_meta": {},
  "events": [],
  "drawings": [],
  "alerts": [],
  "script_id": "16hex",
  "run_id": "16hex",
  "count": 0,
  "mode": "compile",
  "auto_backend": "compile",
  "data_source": "chart",
  "alert_forward": {
    "forwarded": 0,
    "failed": 0,
    "filter": "last_bar",
    "url": "https://hooks.example.com/pine",
    "batch": true
  }
}
FieldMeaning
plotsPrimary series (first plot) — backward compatible list
seriesNamed multi-plot map title → values per bar
plot_metaPer-title color, linewidth, index
eventsStrategy events with script_id / run_id stamps
drawingsExported line/label/box registry for AXIS
alertsalert() / true alertcondition() firings (interpret; empty on compile)
alert_conditionsOptional full condition evaluations (when present)
alert_forwardPresent when a webhook URL was configured; delivery summary
script_idsha256(source)[:16]
run_idPer-Runtime instance id
auto_backendcompile | interpret when requested mode was auto
compile_fallback_reasonWhy auto used interpret (eligibility or compile error)
object_mode / compile_cached / compile_msCompile diagnostics when that path ran
nopython_fallback_reasonNumeric JIT failed; engine re-emitted object mode (still compile)

Errors

HTTPcodeWhen
400MISSING_FIELD / INVALID_FIELD / UNKNOWN_FIELDS / INVALID_BODYSchema
400NO_SCRIPT / NO_DATAEmpty after defaults
400DATA_SOURCE_ERRORresolve_request_sources failed
400WEBHOOK_URL_BLOCKEDwebhook_url is private/loopback/metadata (SSRF denylist)
403DATA_SOURCE_FORBIDDENFree path rejected live provider (ccxt / yahoo / …) — only when FREE_TIER_LIMITS is on
413TOO_MANY_BARS / SCRIPT_TOO_LARGEExceeded FREE_MAX_BARS (5000) or FREE_MAX_SCRIPT_CHARS (256 KiB) — only when FREE_TIER_LIMITS is on
429RATE_LIMITED / TOO_MANY_REQUESTSFree IP rate (60 / 60s) or concurrency (4) — only when FREE_TIER_LIMITS is on
500EXECUTION_ERRORRuntime returned error key; body may include error_kind / error_type / error_bar

POST /run/batch

Request (RUN_BATCH_SCHEMA)

FieldTypeRequiredNotes
scriptslistyesstrings or {id, script} objects
datalistyesShared OHLCV
symbol / data_source / data_options / mode / profileroptionalsame as /run (no inputs)
librarieslistnosame AXIS git-publish list as /run (max 32; applied to every script)
timeout_secondsnumbernosame optional interpret budget as /run (applied to every script)
webhook_url / forward_alerts / alert_last_bar / alert_batchoptionalsame L2 webhook fields as /run (applied per script result)

Hard cap: RUN_BATCH_MAX_SCRIPTS = 8TOO_MANY_SCRIPTS.

Response

{
  "status": "success" | "partial",
  "results": [ { "id": "…", "status": "success|error", … } ],
  "count": 2,
  "ok": 1,
  "data_source": "chart"
}

HTTP 200 for partial success (per-script errors inside results). Envelope validation failures still 400.

Internals

runtime = Runtime(symbol=str(symbol))
result = runtime.run(script, ohlcv, data_feed=…, data_provider=…, mode=…)

See runtime bridge for bar-loop details and compile mode.

Paths: backend/app.py (run_pine_script, run_pine_script_batch, compile_prewarm, WS /ws/run), backend/middleware/schemas.py, backend/middleware/free_limits.py, backend/alert_forwarder.py.

Invariants and edge cases

  1. No API key on these routes — free-tier guards apply only when FREE_TIER_LIMITS is on (see Pro API usage); protect further at the edge if needed.
  2. Batch isolates exceptions per script (try/except around runtime.run).
  3. Whitespace-only data_source coerced to None → chart default; free paths reject live providers (ccxt/yahoo/…) when FREE_TIER_LIMITS is on.
  4. Compile mode may omit alert side effects (alerts: []); use interpret/auto for alerts.
  5. Schema rejects extras — clients must not send AXIS-only fields without updating schema.
  6. Webhook delivery is best-effort — evaluate still returns status: success if the hook fails; check alert_forward.
  7. Structured errors may include error_kind (parse|compile|runtime|data|order|mode), error_type, error_bar. timed_out is set on the HTTP body when Runtime exceeds timeout_seconds.

Worked example

curl -s http://127.0.0.1:5002/run \
  -H 'Content-Type: application/json' \
  -d '{
    "script": "//@version=6\nindicator(\"t\")\nplot(close)",
    "data": [
      {"time": 1, "open": 1, "high": 2, "low": 0.5, "close": 1.5, "volume": 10},
      {"time": 2, "open": 1.5, "high": 2.5, "low": 1.0, "close": 2.0, "volume": 12}
    ],
    "symbol": "TEST:DEMO"
  }'

Failure modes

SymptomCause
UNKNOWN_FIELDSTypo’d property (e.g. ohlcv instead of data)
Parse errors in messageInvalid Pine — same engine as CLI
Batch status: partialAt least one script failed; inspect results[i].message

POST /compile/prewarm

Free readiness hook (same rate/concurrency gates). Body optional:

{ "scripts": ["//@version=6\nindicator(\"x\")\nplot(close)"], "force": false }

Caps at 16 sources. Returns has_numba, builtins_warmed, scripts_ok / scripts_failed, prewarm_ms. Does not execute on OHLCV.

WS /ws/run

When flask-sock is installed: JSON text frames {type: "run", id, script, data, mode?}. Reply {type: "result", …} or {type: "error"}. Ping/pong supported. Same execute_run_payload as HTTP /run.

Alert webhooks (L2)

# Server default (optional)
export ALERT_WEBHOOK_URL=https://hooks.example.com/pine

curl -s http://127.0.0.1:5002/run \
  -H 'Content-Type: application/json' \
  -d '{
    "script": "//@version=6\nindicator(\"a\")\nif bar_index == last_bar_index\n    alert(\"fire\")\nplot(close)",
    "mode": "interpret",
    "webhook_url": "https://hooks.example.com/pine",
    "data": [
      {"time": 1, "open": 1, "high": 2, "low": 0.5, "close": 1.5, "volume": 10},
      {"time": 2, "open": 1.5, "high": 2.5, "low": 1.0, "close": 2.0, "volume": 12}
    ]
  }'

Full engine rules and batch payload shape: Alerts.

See also