{"openapi":"3.1.0","info":{"title":"IDX Screener API","description":"\nREST access to the IDX Screener platform: the screening models, and the\nmarket-data layer they are built on.\n\n### Authentication\nEvery endpoint except `/health` requires an API key:\n\n    Authorization: Bearer idxs_live_xxxxxxxx\n\nKeys are issued with `python scripts/api_keys.py create`, and carry scopes:\n\n| Scope | Grants |\n|---|---|\n| `models:read` | List models, read parameter schemas |\n| `models:run` | Submit model runs, read your jobs |\n| `data:read` | Quotes, snapshots, broker flow, nego, market overview |\n| `usage:read` | Your own usage history and your own end users |\n| `usage:admin` | Usage across all keys and channels (operator only) |\n\n### Attributing runs to your users\nAn API key identifies your *application*, not a person. To record which of your\nend users a run was for, send an opaque, stable id per user:\n\n    X-End-User: user_8417\n\nThis header is **required** on `POST /v1/models/{id}/runs`. Read the history back\nfrom `/v1/usage/events`.\n\n### Running a model\nModel runs retrain on every call and take seconds to minutes, so they are\nasynchronous: `POST /v1/models/{id}/runs` returns `202` with a `job_id`, and you\npoll `GET /v1/jobs/{job_id}` until `status` is `succeeded` or `failed`. For the\nfaster models you can pass `?wait=20` to get the result inline instead.\n\n### Errors\nFailures share one shape, with a stable machine-readable `code`:\n\n    {\"error\": {\"code\": \"invalid_api_key\", \"message\": \"...\", \"request_id\": \"req_...\"}}\n\nQuote `request_id` (also returned as the `X-Request-ID` header) when reporting a\nproblem — it matches the server log line.\n","version":"1.0.0"},"paths":{"/api/health":{"get":{"tags":["system"],"summary":"Liveness and readiness","description":"Unauthenticated health check.\n\n`data_loaded` reports whether the feature cache is warm. Immediately after a\nrestart it is false, and the first data request will pay a one-off cost to\nread the dataset from disk.","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/scopes":{"get":{"tags":["system"],"summary":"List the scopes this API defines","description":"The scopes an API key can carry, and what each one grants.","operationId":"scopes_api_v1_scopes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/models":{"get":{"tags":["models"],"summary":"List available models","description":"Return every model exposed by this API, with its id, display name,\ncategory and version.\n\nUse the `id` from this list wherever an endpoint takes `{model_id}`.","operationId":"list_models_api_v1_models_get","responses":{"200":{"description":"The models this API key can run.","content":{"application/json":{"schema":{}}}}},"security":[{"ApiKeyBearer":[]}]}},"/api/v1/models/{model_id}":{"get":{"tags":["models"],"summary":"Get one model's details","description":"Full detail for a single model: metadata, its parameter schema, and the\nplain-language explanation of what the model does and when to use it.","operationId":"get_model_api_v1_models__model_id__get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","description":"Model id, e.g. `gross_alpha_v10`.","title":"Model Id"},"description":"Model id, e.g. `gross_alpha_v10`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/models/{model_id}/params":{"get":{"tags":["models"],"summary":"Get a model's parameter schema","description":"The machine-readable parameter schema: for each parameter, its `type`,\n`default`, and where applicable `min`, `max` and `options`.\n\nEvery key you send in a run request must appear here — unknown keys are\nrejected with 422 rather than silently ignored.","operationId":"get_model_params_api_v1_models__model_id__params_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","description":"Model id, e.g. `gross_alpha_v10`.","title":"Model Id"},"description":"Model id, e.g. `gross_alpha_v10`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/models/{model_id}/runs":{"post":{"tags":["models"],"summary":"Submit a model run","description":"Start a model run.\n\nRuns are asynchronous because they retrain the model on every call: a warm\nrun takes roughly 9-16 seconds depending on the model, and the first run\nafter a restart also pays the cost of loading the dataset. The response is a\njob id you poll with `GET /v1/jobs/{job_id}`.\n\nFor the faster models, pass `?wait=20` to have the server hold the connection\nand return the result inline when it is ready — with an automatic fall back\nto the job id if it is not.","operationId":"submit_run_api_v1_models__model_id__runs_post","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","description":"Model id, e.g. `gross_alpha_v10`.","title":"Model Id"},"description":"Model id, e.g. `gross_alpha_v10`."},{"name":"view","in":"query","required":false,"schema":{"type":"string","description":"`full` (default) returns the model's native result, including every raw feature column. `compact` returns a card-ready shape for client apps: only the fields a result card renders, with entry/stop/target levels derived from the parameters this run actually used. Supported for `model_f_combined` and `recovery_v4`.","default":"full","title":"View"},"description":"`full` (default) returns the model's native result, including every raw feature column. `compact` returns a card-ready shape for client apps: only the fields a result card renders, with entry/stop/target levels derived from the parameters this run actually used. Supported for `model_f_combined` and `recovery_v4`."},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":0,"description":"Optionally block up to this many seconds for the run to finish. If it completes in time you get 200 with the result inline; otherwise 202 and the job id, exactly as with wait=0.","default":0,"title":"Wait"},"description":"Optionally block up to this many seconds for the run to finish. If it completes in time you get 200 with the result inline; otherwise 202 and the job id, exactly as with wait=0."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunRequest"}}}},"responses":{"202":{"description":"202 with a job id, or 200 with the result if `wait` was satisfied.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs":{"get":{"tags":["jobs"],"summary":"List your recent jobs","description":"Your most recent jobs, newest first — live ones and historical ones together.\nResults are omitted here to keep the response small; fetch a single job for\nits result or summary.\n\nOnly jobs submitted with this API key are returned.","operationId":"list_jobs_api_v1_jobs_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"How many jobs to return.","default":20,"title":"Limit"},"description":"How many jobs to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}":{"get":{"tags":["jobs"],"summary":"Poll a job","description":"Current state of one job.\n\n`status` is one of `queued`, `running`, `succeeded`, `failed` or\n`interrupted` (started but never reported back — the server died mid-run).\n\n`source` tells you which tier answered:\n\n  * `live`   — `result` is present when `status` is `succeeded`.\n  * `ledger` — `result_available` is `false` and you get `summary` instead.\n    The full picks are gone; re-run if you need them.\n\nBranch on `result_available`, not on `status`. Poll every 2-5 seconds; there\nis no benefit to polling faster.","operationId":"get_job_api_v1_jobs__job_id__get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","description":"Job id returned when the run was submitted.","title":"Job Id"},"description":"Job id returned when the run was submitted."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/quotes":{"get":{"tags":["data"],"summary":"Latest close and daily change","description":"Latest close price and day-on-day change for each ticker, from the most\nrecent trading day present in the dataset.\n\nTickers with no data on that date are omitted from the response rather than\nreturned as nulls, so check for presence before indexing.","operationId":"get_quotes_api_v1_data_quotes_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"tickers","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated ticker codes, e.g. `BBCA,TLKM,ANTM`. Max 50.","examples":["BBCA,TLKM"],"title":"Tickers"},"description":"Comma-separated ticker codes, e.g. `BBCA,TLKM,ANTM`. Max 50."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/stocks/{ticker}":{"get":{"tags":["data"],"summary":"Per-stock snapshot with indicators","description":"Recent OHLCV plus every computed feature for one stock: moving averages,\nRSI, volume ratios, and the broker-flow derived fields (bandar net flow,\nsmart-money divergence, foreign/retail net value).","operationId":"get_snapshot_api_v1_data_stocks__ticker__get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":250,"minimum":1,"description":"Trading days of history.","default":20,"title":"Days"},"description":"Trading days of history."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/compare":{"post":{"tags":["data"],"summary":"Compare several stocks side by side","description":"Snapshot several stocks in one call. Limited to 10 tickers per request.","operationId":"compare_api_v1_data_compare_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyBearer":[]}]}},"/api/v1/data/market-overview":{"get":{"tags":["data"],"summary":"Market regime, breadth and movers","description":"Whole-market view: the current regime (bullish/sideways/bearish) and its\nscore, advance-decline breadth, top gainers and losers, and aggregate stats.","operationId":"market_overview_api_v1_data_market_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"ApiKeyBearer":[]}]}},"/api/v1/data/stocks/{ticker}/brokers":{"get":{"tags":["data"],"summary":"Top buyers and sellers for one day","description":"Top-N buying and selling brokers for one ticker on one date, with each\nbroker's buy/sell shares and value, average prices, net position, and\nretail/foreign classification — plus foreign and retail net aggregates.","operationId":"broker_flow_api_v1_data_stocks__ticker__brokers_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"ISO date (YYYY-MM-DD) or `today`.","default":"today","title":"Date"},"description":"ISO date (YYYY-MM-DD) or `today`."},{"name":"market","in":"query","required":false,"schema":{"type":"string","description":"`REG` (regular) or `NEGO` (negotiated).","default":"REG","title":"Market"},"description":"`REG` (regular) or `NEGO` (negotiated)."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many brokers per side.","default":10,"title":"Top N"},"description":"How many brokers per side."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Rank by `value` (IDR) or `shares`.","default":"value","title":"Sort By"},"description":"Rank by `value` (IDR) or `shares`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/stocks/{ticker}/brokers/window":{"get":{"tags":["data"],"summary":"Broker activity over N days","description":"Per-broker activity summed over a window, which surfaces brokers quietly\naccumulating across many days rather than spiking once.","operationId":"broker_window_api_v1_data_stocks__ticker__brokers_window_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":1,"description":"Trading days to sum across.","default":10,"title":"Days"},"description":"Trading days to sum across."},{"name":"market","in":"query","required":false,"schema":{"type":"string","description":"`REG` or `NEGO`.","default":"REG","title":"Market"},"description":"`REG` or `NEGO`."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":10,"title":"Top N"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Rank by `value` or `shares`.","default":"value","title":"Sort By"},"description":"Rank by `value` or `shares`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/stocks/{ticker}/brokers/{broker_code}/timeline":{"get":{"tags":["data"],"summary":"One broker's daily flow on one stock","description":"Day-by-day net flow of one broker on one stock, plus derived signals: the\nfirst net-buy day, the first buy above 1bn IDR, and where the current\naccumulation or distribution streak began.","operationId":"broker_timeline_api_v1_data_stocks__ticker__brokers__broker_code__timeline_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"broker_code","in":"path","required":true,"schema":{"type":"string","description":"Broker code, e.g. `YP`.","title":"Broker Code"},"description":"Broker code, e.g. `YP`."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":250,"minimum":1,"description":"Calendar days to cover.","default":30,"title":"Days"},"description":"Calendar days to cover."},{"name":"market","in":"query","required":false,"schema":{"type":"string","description":"`REG` or `NEGO`.","default":"REG","title":"Market"},"description":"`REG` or `NEGO`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/brokers/{broker_code}/stocks":{"get":{"tags":["data"],"summary":"What one broker has been trading","description":"Which stocks a given broker net-accumulated or net-distributed most over the\nwindow — \"what has YP been buying this week?\"","operationId":"stocks_by_broker_api_v1_data_brokers__broker_code__stocks_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"broker_code","in":"path","required":true,"schema":{"type":"string","description":"Broker code, e.g. `YP`.","title":"Broker Code"},"description":"Broker code, e.g. `YP`."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":1,"description":"Trading days to look back.","default":5,"title":"Days"},"description":"Trading days to look back."},{"name":"market","in":"query","required":false,"schema":{"type":"string","description":"`REG` or `NEGO`.","default":"REG","title":"Market"},"description":"`REG` or `NEGO`."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Top N"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Rank by `value` or `shares`.","default":"value","title":"Sort By"},"description":"Rank by `value` or `shares`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/flows":{"get":{"tags":["data"],"summary":"Market-wide institutional flow scan","description":"Rank the whole market by net institutional flow over a window — the\n\"who is being accumulated right now\" scan.","operationId":"flows_api_v1_data_flows_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"flow_type","in":"query","required":false,"schema":{"type":"string","description":"`foreign`, `retail`, `bandar` — whose flow to rank by.","default":"foreign","title":"Flow Type"},"description":"`foreign`, `retail`, `bandar` — whose flow to rank by."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":1,"default":5,"title":"Days"}},{"name":"min_abs_val","in":"query","required":false,"schema":{"type":"number","description":"Minimum absolute net value (IDR).","default":1000000000,"title":"Min Abs Val"},"description":"Minimum absolute net value (IDR)."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":15,"title":"Top N"}},{"name":"min_price","in":"query","required":false,"schema":{"type":"integer","description":"Exclude stocks below this price (IDR).","default":200,"title":"Min Price"},"description":"Exclude stocks below this price (IDR)."},{"name":"min_daily_val","in":"query","required":false,"schema":{"type":"number","description":"Minimum average daily value (IDR).","default":1000000000,"title":"Min Daily Val"},"description":"Minimum average daily value (IDR)."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"`value` or `shares`.","default":"value","title":"Sort By"},"description":"`value` or `shares`."},{"name":"pattern","in":"query","required":false,"schema":{"type":"string","description":"`total` for net flow, or `consistent` for steady daily accumulation.","default":"total","title":"Pattern"},"description":"`total` for net flow, or `consistent` for steady daily accumulation."},{"name":"min_accum_day_ratio","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"default":0.6,"title":"Min Accum Day Ratio"}},{"name":"max_price_change_pct","in":"query","required":false,"schema":{"type":"number","description":"Skip names that already ran this far.","default":6.0,"title":"Max Price Change Pct"},"description":"Skip names that already ran this far."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/stocks/{ticker}/nego":{"get":{"tags":["data"],"summary":"Nego activity for one stock","description":"Daily negotiated-market value, average price and premium for one stock.\n`nego_premium` is (nego average price - close) / close, so a positive number\nmeans the negotiated block traded above the regular-market close.","operationId":"nego_activity_api_v1_data_stocks__ticker__nego_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":250,"minimum":1,"default":20,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/stocks/{ticker}/nego/brokers":{"get":{"tags":["data"],"summary":"Top nego brokers for one stock","description":"Broker breakdown of the negotiated market for one stock on one date.","operationId":"nego_brokers_api_v1_data_stocks__ticker__nego_brokers_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"ISO date (YYYY-MM-DD) or `today`.","default":"today","title":"Date"},"description":"ISO date (YYYY-MM-DD) or `today`."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":10,"title":"Top N"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"`value` or `shares`.","default":"value","title":"Sort By"},"description":"`value` or `shares`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/nego":{"get":{"tags":["data"],"summary":"Stocks with significant nego activity on a date","description":"Stocks with notable negotiated-market activity on one date, largest first.","operationId":"nego_stocks_api_v1_data_nego_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"ISO date (YYYY-MM-DD) or `today`.","default":"today","title":"Date"},"description":"ISO date (YYYY-MM-DD) or `today`."},{"name":"min_val","in":"query","required":false,"schema":{"type":"number","description":"Minimum nego value (IDR).","default":1000000000,"title":"Min Val"},"description":"Minimum nego value (IDR)."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Top N"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/nego/window":{"get":{"tags":["data"],"summary":"Stocks with the most nego activity over N days","description":"The same scan as `/v1/data/nego`, aggregated across a window of days.","operationId":"nego_stocks_window_api_v1_data_nego_window_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":1,"description":"Calendar days to cover.","default":14,"title":"Days"},"description":"Calendar days to cover."},{"name":"min_val","in":"query","required":false,"schema":{"type":"number","description":"Minimum nego value (IDR).","default":1000000000,"title":"Min Val"},"description":"Minimum nego value (IDR)."},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Top N"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/stocks/{ticker}/turning-point":{"get":{"tags":["data"],"summary":"Turning-point signal for one stock","description":"Whether a stock is showing a reversal setup: price down over the window,\nselling drying up, and broker flow flipping from distribution to accumulation.","operationId":"turning_point_api_v1_data_stocks__ticker__turning_point_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"Ticker code, e.g. `BBCA`.","title":"Ticker"},"description":"Ticker code, e.g. `BBCA`."},{"name":"lookback","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":2,"description":"Window in trading days.","default":10,"title":"Lookback"},"description":"Window in trading days."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/data/turning-points":{"post":{"tags":["data"],"summary":"Turning-point signals for many stocks","description":"Batch version of the turning-point check. Loads the feature data once, so\nthis is much cheaper than calling the single-ticker endpoint in a loop.","operationId":"turning_points_api_v1_data_turning_points_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurningPointRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyBearer":[]}]}},"/api/v1/usage/events":{"get":{"tags":["usage"],"summary":"Your model runs","description":"Every model run made with your API key, newest first.\n\nEach row records who it was for (`end_user`, as you asserted it), which model,\nthe exact parameters used, how long it took, and a summary of what came back —\npick count, the tickers picked, and the trading date analysed.\n\nPaginate by passing the previous response's `next_cursor`.","operationId":"list_usage_events_api_v1_usage_events_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"end_user","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one of your end users.","title":"End User"},"description":"Filter to one of your end users."},{"name":"model_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one model id.","title":"Model Id"},"description":"Filter to one model id."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`started`, `succeeded`, `failed` or `interrupted`.","title":"Status"},"description":"`started`, `succeeded`, `failed` or `interrupted`."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/datetime or unix seconds.","title":"Since"},"description":"ISO date/datetime or unix seconds."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/datetime or unix seconds.","title":"Until"},"description":"ISO date/datetime or unix seconds."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page.","title":"Cursor"},"description":"`next_cursor` from the previous page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/usage/events/{event_id}":{"get":{"tags":["usage"],"summary":"One of your runs","description":"One run in full. An event belonging to another key reads as 404.","operationId":"get_usage_event_api_v1_usage_events__event_id__get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","description":"Event id, e.g. `ue_3f1c9ab27d4e5601`.","title":"Event Id"},"description":"Event id, e.g. `ue_3f1c9ab27d4e5601`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/usage/summary":{"get":{"tags":["usage"],"summary":"Your usage, aggregated","description":"Run counts, success and failure totals, and average duration, grouped by\nmodel, end user, status or day.","operationId":"usage_summary_api_v1_usage_summary_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"group_by","in":"query","required":false,"schema":{"type":"string","description":"One of: model, end_user, status, channel, day (admin also: api_key, actor)","default":"model","title":"Group By"},"description":"One of: model, end_user, status, channel, day (admin also: api_key, actor)"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Until"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/usage/end-users":{"get":{"tags":["usage"],"summary":"Which of your users have used this","description":"The distinct end-user ids you have asserted, with run counts, first and last\nseen, and which models each has used.","operationId":"list_usage_end_users_api_v1_usage_end_users_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/usage/end-users/{end_user_id}/events":{"get":{"tags":["usage"],"summary":"One end user's history","description":"Everything one of your end users has run. This is `/events?end_user=...` with\nthe id in the path — the shape an app reaches for when building a per-user\nhistory screen.","operationId":"end_user_events_api_v1_usage_end_users__end_user_id__events_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"end_user_id","in":"path","required":true,"schema":{"type":"string","description":"The end-user id you asserted.","title":"End User Id"},"description":"The end-user id you asserted."},{"name":"model_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Until"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/usage/events":{"get":{"tags":["admin"],"summary":"All runs, every key and channel","description":"Every recorded model run across all channels, including Telegram chats and\nthe scheduled daily broadcast. Includes the identity columns the tenant\nendpoints withhold.","operationId":"admin_events_api_v1_admin_usage_events_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"api_key_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one API key.","title":"Api Key Id"},"description":"Filter to one API key."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`api`, `telegram` or `scheduled`.","title":"Channel"},"description":"`api`, `telegram` or `scheduled`."},{"name":"end_user","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End User"}},{"name":"model_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Until"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/usage/summary":{"get":{"tags":["admin"],"summary":"All usage, aggregated","description":"Cross-channel rollup. `group_by=channel` answers 'where does load come from'.","operationId":"admin_summary_api_v1_admin_usage_summary_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"group_by","in":"query","required":false,"schema":{"type":"string","description":"One of: model, end_user, status, channel, day (admin also: api_key, actor)","default":"model","title":"Group By"},"description":"One of: model, end_user, status, channel, day (admin also: api_key, actor)"},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"}},{"name":"api_key_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Until"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/usage/end-users":{"get":{"tags":["admin"],"summary":"All end users, across keys","description":"Every end user any consuming app has asserted, with the owning key.","operationId":"admin_end_users_api_v1_admin_usage_end_users_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"api_key_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/usage/keys":{"get":{"tags":["admin"],"summary":"Per-key rollup","description":"Runs per API key — who is actually using the API, and how much.","operationId":"admin_keys_api_v1_admin_usage_keys_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/consent":{"get":{"tags":["consent"],"summary":"Has this user accepted the disclaimer?","description":"Check the gate before showing the feature.\n\n`required_version` tells you which version to display. When\n`accepted` is false — never accepted, or accepted an older version — show the\ndisclaimer and block until they agree.","operationId":"get_consent_api_v1_consent_get","security":[{"ApiKeyBearer":[]}],"parameters":[{"name":"document_id","in":"query","required":false,"schema":{"type":"string","description":"Document to check.","default":"fair_disclaimer","title":"Document Id"},"description":"Document to check."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["consent"],"summary":"Record that the user accepted","description":"Record an acceptance. Call this when the user ticks the box and continues.\n\nAppend-only: re-accepting writes a new row rather than overwriting, so the\nfull history of what was agreed and when is preserved.\n\nUnlike usage recording, a failure here is a hard error — a consent that was\nnot stored did not happen, and your gate must stay closed.","operationId":"post_consent_api_v1_consent_post","security":[{"ApiKeyBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CompareRequest":{"properties":{"tickers":{"items":{},"type":"array","minItems":1,"title":"Tickers","description":"Ticker codes to compare side by side. Max 10."},"days":{"type":"integer","maximum":250.0,"minimum":1.0,"title":"Days","description":"Trading days of history per ticker.","default":20}},"type":"object","required":["tickers"],"title":"CompareRequest","description":"Body for POST /v1/data/compare.","examples":[{"days":20,"tickers":["BBCA","BBRI"]}]},"ConsentRequest":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Which document was accepted.","default":"fair_disclaimer"},"document_version":{"type":"string","title":"Document Version","description":"Version string of the text actually shown to the user."},"accepted":{"type":"boolean","title":"Accepted","description":"Must be true to record an acceptance.","default":true},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale","description":"Locale of the text shown, e.g. `id-ID`."},"client_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Version","description":"Your app's version, for audit."}},"type":"object","required":["document_version"],"title":"ConsentRequest","description":"Body for POST /v1/consent.","examples":[{"accepted":true,"client_version":"1.4.0","document_id":"fair_disclaimer","document_version":"2026-09-01","locale":"id-ID"}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"RunRequest":{"properties":{"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params","description":"Parameter overrides for this run. Any parameter you omit uses the model's default, as reported by GET /v1/models/{model_id}/params. Unknown parameter names are rejected with 422."}},"type":"object","title":"RunRequest","description":"Body for POST /v1/models/{model_id}/runs.","examples":[{"params":{"max_picks":5,"min_probability":70,"retrain":false}}]},"TurningPointRequest":{"properties":{"tickers":{"items":{},"type":"array","minItems":1,"title":"Tickers","description":"Ticker codes to evaluate, e.g. [\"BBCA\", \"TLKM\"]. Max 50."},"lookback":{"type":"integer","maximum":60.0,"minimum":2.0,"title":"Lookback","description":"Window in trading days used to detect the turn.","default":10}},"type":"object","required":["tickers"],"title":"TurningPointRequest","description":"Body for POST /v1/data/turning-points.","examples":[{"lookback":10,"tickers":["BBCA","TLKM","ANTM"]}]},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"ApiKeyBearer":{"type":"http","scheme":"bearer","description":"Your API key, sent as `Authorization: Bearer <key>`."},"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Alternative to the Authorization header."}}},"tags":[{"name":"models","description":"Model catalogue, schemas and run submission."},{"name":"jobs","description":"Poll asynchronous model runs."},{"name":"data","description":"Market data: quotes, broker flow, nego, turning points."},{"name":"consent","description":"Disclaimer acceptance gate."},{"name":"system","description":"Health and diagnostics. No authentication required."}],"security":[{"ApiKeyBearer":[]},{"ApiKeyHeader":[]}]}