# Fundai Data API

> Iran stock market data (TSE/IFB, Codal filings, IME) over HTTPS JSON. Base URL: https://fundai.ir/api/data/v1

## Rules an agent must follow

- The API is for personal, non-commercial use: the account holder's own research, analysis and trading decisions. Using the data in a product, site, app or service for others, reselling it or republishing it needs a formal request at https://fundai.ir/enterprise and Fundai's written approval. Do not build commercial products on it without that agreement.
- Authenticate every call with `Authorization: Bearer <key>`. Never print, log or send the key anywhere else. Never call from a browser.
- Every successful (2xx) call costs its dataset's price once for each started block of rows (`per_rows` in `GET /datasets/`; daily trading 30, everything else 20): 21 screener rows cost twice the price, and the default page is one block. `X-Credits-Charged` says what a call cost. Errors, 304s, repeats and empty answers are free. Check `GET /me/` (free) for the balance before a large job.
- Plan before calling: fetch only the company, date range and dataset the question needs. Use `from`/`to` and `limit`; never page through a whole history to answer one number.
- Identify a company with `GET /companies/?symbol=<symbol>` (or pass the symbol straight in the path). Paths take the `slug` or the symbol.
- Paginate with `cursor`: send back `next_cursor` exactly; stop when it is null.
- On 429, wait the `Retry-After` seconds, then retry. Respect `X-RateLimit-Remaining`. Run no more requests in parallel than `limits.concurrency` in `GET /me/`: limits follow your usage tier. The tier rises with the credits you spend, not the credits you buy, and a new tier takes effect within 10 minutes.
- Run big jobs between 00:00 and 07:00 Tehran time: the site is quiet then, so your per-minute and in-flight limits double (`limits.night` in `GET /me/`). Daily limits and prices stay the same.
- Self-service accounts read up to `limits.company_cap` different companies a Jalali month (`limits.companies_used` in `GET /me/`; re-reading one costs no cap) and `limits.history_years` years back; older rows are left out and `meta.history_from` says where the answer starts. Past the cap a new company is 403 `api-company-cap` (free). Bulk or full-market access is a separate contract: https://fundai.ir/enterprise. Pick the companies the question needs; never walk the company list.
- A key starting `fai-test_` is a sandbox key (accounts that have bought credit): every data endpoint answers with fixed sample data (`meta.sandbox: true` with a `meta.notice`, `X-Sandbox: true`; over MCP a warning comes first), free, at 10 requests a minute and 500 a day. Never present sandbox figures as real market data; use a live `fai-api_` key for real answers.
- Send an `Idempotency-Key` on any request you might retry (always on POST /screener/); a retry with the same key is free.
- On 402 `insufficient-credit`, stop and tell the user to buy credits at https://fundai.ir/packages#api.
- Quote figures with their unit and period. Field names carry the unit (`_rial`, `_million_rial`, `_pct`, `_thousand_usd`); `unit` fields say it otherwise. Percent fields are percents (12.5 = 12.5%).
- Dates: `date` is ISO Gregorian, `date_jalali` the Iranian calendar. Iranian readers expect Jalali.
- `raw` datasets are as the source published them; `processed` ones were built by Fundai (standardised, trailing sums, ratios). Say which you used.
- Figures are data, not advice. Do not forecast prices.

## Response shape

Lists: `{"data": [...], "next_cursor": "..."|null, "meta": {...}}`. One object: `{"data": {...}, "meta": {...}}`. Errors: `{"detail": "<key>"}` (validation adds `errors`). Unknown values are `null`; numbers carry at most four decimals, and counts, volumes and rial amounts none.

`meta.updated_at` (ISO 8601) is when the data behind the answer last changed: for one company, its own rows. Tell the user how fresh a figure is. An empty list is free and carries `meta.empty`: `no-data` when we have no rows of that dataset for the company at all, `no-match` when rows exist but none fit your filters (widen `from`/`to`).

## MCP

The same data is served over MCP for AI assistants: a remote server at https://fundai.ir/api/data/mcp/ (streamable HTTP, JSON-RPC, stateless). Send the same `Authorization: Bearer <key>` header. Each tool is one endpoint below, named by its key (`prices`, `statements`, `screener`, and the free `me`, `datasets`, `usage`); a tool call costs exactly what the matching request costs and is free when it fails. Path parameters such as `company` are tool arguments.

## Errors

- 400 `validation-error`: A parameter is wrong; `errors` names it.
- 400 `api-cursor-invalid`: Send `next_cursor` back as it came.
- 400 `api-idempotency-key-invalid`: `Idempotency-Key` is over 100 characters.
- 401 `api-key-missing`: No key in `Authorization` or `X-API-Key`.
- 401 `api-key-invalid`: Unknown, disabled or expired key.
- 402 `insufficient-credit`: Not enough credit for this request. Nothing was charged.
- 403 `api-client-suspended`: Your API access is suspended.
- 403 `api-origin-not-allowed`: Called from a browser origin not on your list.
- 403 `api-dataset-unavailable`: This dataset is not open to your account.
- 404 `company-not-found`: No listed company with that slug or symbol.
- 409 `api-idempotency-key-reused`: This `Idempotency-Key` was used for a different request.
- 409 `api-idempotency-in-progress`: The first request with this `Idempotency-Key` is still running.
- 403 `api-company-cap`: This month's company cap is reached: reading a new company waits for next month (Jalali); companies already read this month stay open. Wider access is a contract.
- 403 `api-sandbox-needs-purchase`: Test keys work once the account has bought credit; until then, try the API with the trial credits.
- 429 `api-rate-limited`: Per-minute limit; wait `Retry-After` seconds.
- 429 `api-daily-limit`: Daily limit; resets at midnight Tehran time.
- 429 `api-too-many-parallel`: Too many requests in flight at once.
- 503 `api-busy`: The API as a whole is at its ceiling; retry shortly.
- 503 `api-paused`: The API is paused for maintenance.

## Endpoints

### GET /me/ (free)

Your balance, today's requests and your limits. Free, and a good first call to check a key.

Fields:
- `client` (string): Your account name.
- `key` (object): `{label, masked, sandbox}` of the key you called with; `sandbox` is true for a test key, whose limits below are the sandbox's.
- `credits` (integer, unit credit): Credits left.
- `used_today` (integer): Requests counted today against the daily limit.
- `limits` (object): `tier` (your usage tier), `per_minute`, `per_day`, `concurrency`, `repeat_grace_seconds`, `idempotency_hours`, and `next_tier`: `{level, credits}`, the credits still to spend to reach the next tier, or null at the top. Your tier rises on its own as your calls spend credits. `night`: `{from, to, factor, active}`, the quiet hours (Tehran) when `per_minute` and `concurrency` double, and whether it is now; the figures above already include it.

Example:
```json
{"data": {"client": "user-0193…", "key": {"label": "server", "masked": "fai-api_••••••••Xk2Q", "sandbox": false}, "credits": 4812, "used_today": 188, "limits": {"tier": 2, "per_minute": 60, "per_day": 5000, "concurrency": 4, "repeat_grace_seconds": 60, "idempotency_hours": 24, "next_tier": {"level": 3, "credits": 6400}, "night": {"from": "00:00", "to": "07:00", "factor": 2, "active": false}}}}
```

### GET /datasets/ (free)

Every dataset with its live price, page cap and whether your account can read it. Free.

Fields:
- `key` (string): Dataset id.
- `group` (string): reference, market, statements, activity, corporate or screener.
- `kind` (string): `reference`, `raw` (as the source published it) or `processed` (built by us).
- `method` (string): HTTP method.
- `path` (string): Path under /api/data/v1.
- `cost` (integer, unit credit): Credits for each block of `per_rows` rows a successful request returns.
- `per_rows` (integer): Rows one `cost` buys; a request pays once per started block, so 21 screener rows cost twice.
- `max_limit` (integer): Largest `limit` this dataset accepts.
- `available` (boolean): Whether your account can read it.

Example:
```json
{"data": [{"key": "prices", "group": "market", "kind": "raw", "method": "GET", "path": "/companies/{company}/prices/", "cost": 3, "per_rows": 30, "max_limit": 1000, "available": true}]}
```

### GET /usage/ (free)

Calls and credits per day and dataset, newest first. Free.

Parameters:
- `days` (query, integer): How many days back, 1 to 90. Default `30`.

Fields:
- `date` (date): Day, Tehran time.
- `date_jalali` (string): The same date in the Jalali calendar.
- `dataset` (string, nullable): Dataset id, null for a free endpoint.
- `calls` (integer): Requests that day.
- `charged_calls` (integer): Requests that cost credit.
- `credits` (integer, unit credit): Credits spent.
- `errors` (integer): Requests that failed (never charged).

Example:
```json
{"data": [{"date": "2026-11-07", "dataset": "prices", "calls": 412, "charged_calls": 398, "credits": 398, "errors": 3}]}
```

### GET /companies/ (1 credit per 1000 rows (each started block), reference, max limit 1000)

Every listed company we cover, with the `slug` every other path takes.

Parameters:
- `industry` (query, string): Only this industry's `slug`.
- `symbol` (query, string): Find one symbol.
- `primary` (query, boolean): Leave out second listings (rights, other boards). Default `True`.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `slug` (string): Stable id used in every other path.
- `symbol` (string): Trading symbol as TSETMC writes it.
- `name` (string): Company name in Persian.
- `name_en` (string, nullable): English name, when we have one.
- `ticker_en` (string, nullable): Latin ticker, when we have one.
- `industry` (object, nullable): `{slug, name}` of its industry.
- `tsetmc_id` (string, nullable): The instrument id on tsetmc.com.
- `board` (string, nullable): Market and board as TSETMC words it.
- `trading_state` (string, nullable): `trading` or `halted`.
- `fiscal_year_end` (string, nullable): Month and day the fiscal year ends, Jalali `MM-DD`.
- `national_id` (string, nullable): National id of the company.
- `is_primary_listing` (boolean): False for a second listing of the same company (rights, another board).
- `updated_at` (datetime, nullable): Last change to this row, ISO 8601.
- `updated_at_jalali` (string, nullable): The same date in the Jalali calendar.

Example:
```json
{"data": [{"slug": "foulad", "symbol": "فولاد", "name": "فولاد مبارکه اصفهان", "name_en": "Mobarakeh Steel", "ticker_en": "FOLD", "industry": {"slug": "basic-metals", "name": "فلزات اساسی"}, "tsetmc_id": "46348559193224090", "board": "بازار اول (تابلوی اصلی) بورس", "trading_state": "trading", "fiscal_year_end": "12-29", "national_id": "10260145720", "is_primary_listing": true, "updated_at": "2026-11-06T21:14:03+03:30"}], "next_cursor": "WzEyXQ", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/ (1 credit per answer, reference, max limit 1)

One company's directory row, plus its activity, CEO, auditor and website.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.

Fields:
- `slug` (string): Stable id used in every other path.
- `symbol` (string): Trading symbol as TSETMC writes it.
- `name` (string): Company name in Persian.
- `name_en` (string, nullable): English name, when we have one.
- `ticker_en` (string, nullable): Latin ticker, when we have one.
- `industry` (object, nullable): `{slug, name}` of its industry.
- `tsetmc_id` (string, nullable): The instrument id on tsetmc.com.
- `board` (string, nullable): Market and board as TSETMC words it.
- `trading_state` (string, nullable): `trading` or `halted`.
- `fiscal_year_end` (string, nullable): Month and day the fiscal year ends, Jalali `MM-DD`.
- `national_id` (string, nullable): National id of the company.
- `is_primary_listing` (boolean): False for a second listing of the same company (rights, another board).
- `updated_at` (datetime, nullable): Last change to this row, ISO 8601.
- `updated_at_jalali` (string, nullable): The same date in the Jalali calendar.
- `activity` (string, nullable): Line of business as filed.
- `ceo` (string, nullable): Chief executive.
- `auditor` (string, nullable): Auditor.
- `website` (string, nullable): Website.

Example:
```json
{"data": {"slug": "foulad", "symbol": "فولاد", "name": "فولاد مبارکه اصفهان", "activity": "تولید و فروش انواع محصولات فولادی", "ceo": "…", "auditor": "…", "website": "msc.ir"}, "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /industries/ (1 credit per 500 rows (each started block), reference, max limit 500)

The industry tree, two levels deep.

Parameters:
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `slug` (string): Industry id.
- `name` (string): Name in Persian.
- `name_en` (string, nullable): English name.
- `parent` (string, nullable): Parent industry's slug, null at the top.

Example:
```json
{"data": [{"slug": "basic-metals", "name": "فلزات اساسی", "name_en": "Basic Metals", "parent": null}], "next_cursor": null, "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/prices/ (3 credits per 30 rows (each started block), raw, max limit 1000)

One row per trading session, newest first, as TSETMC published it: prices, volume, value, market cap and the individual/institutional split. Not adjusted for capital raises or dividends.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `from` (query, date): Earliest date, inclusive. Jalali (`1404-01-01`) or Gregorian (`2025-03-21`) both work.
- `to` (query, date): Latest date, inclusive, in either calendar.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `date` (date): Session day, Tehran time.
- `date_jalali` (string): The same day in Jalali.
- `trading_state` (string, nullable): `trading` or `halted`.
- `close_rial` (number, unit rial): Closing price.
- `last_rial` (number, unit rial): Last trade price.
- `previous_close_rial` (number, unit rial): Previous session's close.
- `volume` (number, unit share): Shares traded.
- `value_rial` (number, unit rial): Value traded.
- `trades` (number): Number of trades.
- `market_cap_rial` (number, unit rial): Shares outstanding times close.
- `shares_outstanding` (number, unit share): Shares outstanding.
- `average_volume_30d` (number, unit share, nullable): Mean volume over the last 30 sessions, this one included.
- `individual_buy_volume` (number, unit share): Shares bought by individuals (حقیقی).
- `individual_sell_volume` (number, unit share): Shares sold by individuals.
- `institutional_buy_volume` (number, unit share): Shares bought by institutions (حقوقی).
- `institutional_sell_volume` (number, unit share): Shares sold by institutions.
- `individual_buyers` (number): Individual buyers.
- `individual_sellers` (number): Individual sellers.
- `institutional_buyers` (number): Institutional buyers.
- `institutional_sellers` (number): Institutional sellers.
- `individual_buy_per_trader_rial` (number, unit rial, nullable): Close × individual buy volume ÷ individual buyers.
- `individual_sell_per_trader_rial` (number, unit rial, nullable): The same for sellers.
- `individual_buyer_power` (number, unit ratio, nullable): Buy per trader ÷ sell per trader.
- `individual_net_flow_rial` (number, unit rial): Close × (institutional sell − institutional buy volume). Negative is money leaving.
- Note: Only days with a session appear.
- Note: When a side had no traders the per-trader figures are null, not zero.

Example:
```json
{"data": [{"date": "2026-10-14", "date_jalali": "1405-07-22", "trading_state": "trading", "close_rial": 4540, "last_rial": 4540, "previous_close_rial": 4410, "volume": 1563050927, "value_rial": 7096251208580, "trades": 13401, "market_cap_rial": 8784900000000000, "shares_outstanding": 1935000000000, "average_volume_30d": 3418635750.87, "individual_buy_volume": 1091991421, "individual_sell_volume": 1072498112, "institutional_buy_volume": 471059506, "institutional_sell_volume": 490552815, "individual_buyers": 1517, "individual_sellers": 2887, "institutional_buyers": 91, "institutional_sellers": 16, "individual_buy_per_trader_rial": 3268056065.48, "individual_sell_per_trader_rial": 1686574793.38, "individual_buyer_power": 1.94, "individual_net_flow_rial": 88499622860}], "next_cursor": "WyIyMDI2LTEwLTE0IiwgOTEyMzRd", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/statements/ (10 credits per 20 rows (each started block), raw, max limit 2000)

Statement lines as the company filed them on Codal: balance sheet, income statement (separate and consolidated) and cash flow. Newest period first.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `category` (query, string): One statement only. One of: balance-sheet, income-statement, income-statement-consolidated, cash-flow.
- `duration` (query, string): Period. 3/6/9/12 are filed periods from the start of the fiscal year; s-1 to s-4 single quarters; `filed` and `quarters` select each group. One of: 3, 6, 9, 12, s-1, s-2, s-3, s-4, filed, quarters.
- `include_forecasts` (query, boolean): Add the company's own forecast rows (income statement only). Default `False`.
- `from` (query, date): Earliest period end, inclusive, either calendar.
- `to` (query, date): Latest period end, inclusive.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `company` (string): Company slug.
- `category` (string): Which statement.
- `duration` (string): Period label.
- `months` (integer): Months the period covers.
- `fiscal_quarter` (integer, nullable): 1 to 4 for a quarter, otherwise null.
- `period_end` (date): Last day of the period.
- `period_end_jalali` (string): The same in Jalali.
- `line_slug` (string): Stable line id, the same on every company.
- `line_title` (string): Line name in Persian.
- `unit` (string): `million_rial`, `rial_per_share` (EPS, DPS) or `percent` (margins).
- `value` (number): The figure, in `unit`.
- `is_forecast` (boolean): A forecast, not a reported figure.
- Note: Quarters (s-1…s-4) are computed by us from the filed periods; Codal does not file them.
- Note: Margins are computed from the statement's own lines.
- Note: A missing line means it was not filed. Header and empty lines are left out.

Example:
```json
{"data": [{"company": "foulad", "category": "income-statement", "duration": "6", "months": 6, "fiscal_quarter": null, "period_end": "2026-09-22", "period_end_jalali": "1405-06-31", "line_slug": "operating-income", "line_title": "درآمدهای عملیاتی", "unit": "million_rial", "value": 2509422184, "is_forecast": false}, {"company": "foulad", "category": "income-statement", "duration": "6", "months": 6, "fiscal_quarter": null, "period_end": "2026-09-22", "period_end_jalali": "1405-06-31", "line_slug": "eps", "line_title": "سود (زیان) خالص هر سهم - ریال", "unit": "rial_per_share", "value": 306, "is_forecast": false}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/statements/ttm/ (15 credits per 20 rows (each started block), processed, max limit 1000)

Built by us from the income statement: trailing-twelve-month sums on each fiscal quarter (`basis=ttm`), or each period with its previous quarter, previous year, high, low and average (`basis=period`).

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `basis` (query, string): `ttm` or `period`. One of: ttm, period. Default `ttm`.
- `category` (query, string): Separate or consolidated income statement. One of: income-statement, income-statement-consolidated.
- `duration` (query, string): Period label. TTM rows only have s-1 to s-4. One of: 3, 6, 9, 12, s-1, s-2, s-3, s-4, filed, quarters.
- `from` (query, date): Earliest date, inclusive. Jalali (`1404-01-01`) or Gregorian (`2025-03-21`) both work.
- `to` (query, date): Latest date, inclusive, in either calendar.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `company` (string): Company slug.
- `category` (string): Which statement.
- `basis` (string): `ttm` or `period`.
- `duration` (string): Period label.
- `months` (integer): 12 for TTM.
- `fiscal_quarter` (integer, nullable): Fiscal quarter the period closes.
- `period_end` (date): Last day of the period.
- `period_end_jalali` (string): Jalali.
- `line_slug` (string): Line id.
- `line_title` (string): Line name.
- `unit` (string): As in statements.
- `value` (number): TTM sum, or the period's figure.
- `previous_quarter_value` (number, nullable): Quarter before (period basis).
- `previous_year_value` (number, nullable): Same period a year earlier (period basis).
- `high_value` (number, nullable): Highest value of this line for this period label so far.
- `high_period_end` (date, nullable): When the high was.
- `high_period_end_jalali` (string, nullable): The same date in the Jalali calendar.
- `low_value` (number, nullable): Lowest so far.
- `low_period_end` (date, nullable): When the low was.
- `low_period_end_jalali` (string, nullable): The same date in the Jalali calendar.
- `average_value` (number, nullable): Mean of the non-zero periods.
- `average_periods` (integer, nullable): How many periods went into the mean.
- Note: A fixed set of lines is built: operating revenue, gross profit, net profit, margins and EPS.

Example:
```json
{"data": [{"company": "foulad", "category": "income-statement", "basis": "ttm", "duration": "s-2", "months": 12, "fiscal_quarter": 2, "period_end": "2026-09-22", "period_end_jalali": "1405-06-31", "line_slug": "operating-income", "line_title": "درآمدهای عملیاتی", "unit": "million_rial", "value": 4569548192, "previous_quarter_value": null, "previous_year_value": null, "high_value": null, "high_period_end": null, "low_value": null, "low_period_end": null, "average_value": null, "average_periods": null}], "next_cursor": null, "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/ratios/ (15 credits per 20 rows (each started block), processed, max limit 1000)

Valuation, profitability, leverage, activity and cash-flow ratios per calendar day, newest first. Percent fields end in `_pct` and are percents (35.8 means 35.8%).

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `duration` (query, string): Fiscal quarter of the newest filing used. One of: s-1, s-2, s-3, s-4.
- `from` (query, date): Earliest date, inclusive. Jalali (`1404-01-01`) or Gregorian (`2025-03-21`) both work.
- `to` (query, date): Latest date, inclusive, in either calendar.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `company` (string): Company slug.
- `date` (date): Ratio day (calendar day).
- `date_jalali` (string): The same date in the Jalali calendar.
- `price_date` (date): Session whose close the price ratios use.
- `price_date_jalali` (string): The same date in the Jalali calendar.
- `close_rial` (number, unit rial): That close.
- `period_end` (date, nullable): Period end of the newest filing used.
- `period_end_jalali` (string, nullable): The same date in the Jalali calendar.
- `fiscal_quarter` (integer, nullable): Which fiscal quarter it closes.
- `pe_ttm` (number, unit ratio, nullable): Price ÷ TTM EPS.
- `ps_ttm` (number, unit ratio, nullable): Market cap ÷ TTM revenue.
- `pb` (number, unit ratio, nullable): Market cap ÷ equity.
- `pd_ttm` (number, unit ratio, nullable): Price ÷ expected DPS.
- `peg_ttm` (number, unit ratio, nullable): P/E ÷ operating-profit growth %.
- `pegy_ttm` (number, unit ratio, nullable): PEG with dividend yield added to growth.
- `eps_ttm_rial` (number, unit rial_per_share, nullable): TTM EPS.
- `operating_revenue_ttm_million_rial` (number, unit million_rial, nullable): TTM operating revenue.
- `gross_margin_ttm_pct` (number, unit percent, nullable): Gross margin.
- `net_margin_ttm_pct` (number, unit percent, nullable): Net margin.
- `roa_ttm_pct` (number, unit percent, nullable): Return on assets.
- `roe_ttm_pct` (number, unit percent, nullable): Return on equity.
- `debt_to_equity` (number, unit ratio, nullable): Total debt ÷ equity.
- `debt_to_assets_pct` (number, unit percent, nullable): Total debt ÷ assets.
- `receivable_days_ttm` (number, unit day, nullable): Collection period.
- `inventory_days_ttm` (number, unit day, nullable): Inventory period.
- `operating_cash_flow_ttm_million_rial` (number, unit million_rial, nullable): Operating cash flow.
- `ocf_to_net_profit_ttm` (number, unit ratio, nullable): Operating cash flow ÷ net profit.
- `high_close_rial` (number, unit rial, nullable): Highest stored close up to this day.
- `low_close_rial` (number, unit rial, nullable): Lowest stored close up to this day.
- `high_close_date` (date, nullable): When that high close was.
- `high_close_date_jalali` (string, nullable): The same date in the Jalali calendar.
- `low_close_date` (date, nullable): When that low close was.
- `low_close_date_jalali` (string, nullable): The same date in the Jalali calendar.
- `pd_period` (number, unit ratio, nullable): P/D on the filed period.
- `peg_period` (number, unit ratio, nullable): PEG on the filed period.
- `pegy_period` (number, unit ratio, nullable): PEGY on the filed period.
- `roa_period_pct` (number, unit percent, nullable): ROA on the filed period.
- `roe_period_pct` (number, unit percent, nullable): ROE on the filed period.
- `receivable_days_period` (number, unit day, nullable): Collection period, filed period.
- `inventory_days_period` (number, unit day, nullable): Inventory period, filed period.
- `operating_cash_flow_period_million_rial` (number, unit million_rial, nullable): Operating cash flow, filed period.
- `ocf_to_net_profit_period` (number, unit ratio, nullable): Operating cash flow ÷ net profit, filed period.
- `operating_cash_flow_ttm_thousand_usd` (number, unit thousand_usd, nullable): TTM operating cash flow, free-market dollar.
- `operating_cash_flow_ttm_thousand_usd_nima` (number, unit thousand_usd, nullable): TTM operating cash flow, NIMA dollar.
- `operating_cash_flow_period_thousand_usd` (number, unit thousand_usd, nullable): Filed period's operating cash flow, free-market dollar.
- `operating_cash_flow_period_thousand_usd_nima` (number, unit thousand_usd, nullable): Filed period's operating cash flow, NIMA dollar.
- `ocf_to_net_profit_ttm_usd` (number, unit ratio, nullable): Operating cash flow ÷ net profit, both in free-market dollars.
- `ocf_to_net_profit_ttm_usd_nima` (number, unit ratio, nullable): Operating cash flow ÷ net profit, both in NIMA dollars.

Each `_ttm` ratio also has a `_period` twin measured on the filed period alone, not annualised. Cash flow comes in rial and in thousand USD at the free and NIMA rates (`_thousand_usd`, `_thousand_usd_nima`). Every date has a `_jalali` twin.
- Note: Rows are calendar days: on a holiday the last close carries over (see `price_date`).
- Note: `_period` ratios are not annualised; don't compare them with `_ttm`.

Example:
```json
{"data": [{"company": "foulad", "date": "2026-10-26", "date_jalali": "1405-08-04", "price_date": "2026-10-14", "close_rial": 4540, "period_end": "2026-09-22", "fiscal_quarter": 2, "pe_ttm": 6.551, "ps_ttm": 1.922, "pb": 2.344, "eps_ttm_rial": 693, "gross_margin_ttm_pct": 29.2, "net_margin_ttm_pct": 29.3, "roa_ttm_pct": 17.2, "roe_ttm_pct": 35.8, "debt_to_equity": 1.079, "debt_to_assets_pct": 51.9, "receivable_days_ttm": 153.37, "inventory_days_ttm": 135.8}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### POST /screener/ (25 credits per 20 rows (each started block), processed, max limit 200)

Run the same multi-step filter the Fundai screener runs, with every feature open, and get the matching companies with every figure the steps read. Send the body as JSON; retry safely with an `Idempotency-Key`.

Parameters:
- `filters` (body, array): Steps combined with AND: `[{step, params}]`. Families: `financial-ratio`, `monthly`, `income-statement`, `operating-revenue`, `bourse-kala`, `technical`. A step filters when its params carry `operator` (gt, gte, lt, lte, has) and `value`.
- `groups` (body, array): Optional OR of AND groups, each a list of steps. When sent, it replaces `filters`.
- `sort` (body, string): Engine keys from `meta.columns[].engine_key`, comma separated, `-` for descending.
- `column_filters` (body, array): `[{key, min, max}]` on engine keys, in the engine's units.
- `limit` (body, integer): Rows per page, 20 by default, up to 200. Every 20 rows (each started block) is one price: 21 to 40 rows cost twice.
- `cursor` (body, string): `next_cursor` of the previous page.

Fields:
- `slug` (string): Company slug.
- `symbol` (string): Symbol.
- `name` (string): Company name.
- `industry` (object): `{slug, name}`.
- `board` (string, nullable): bourse, farabourse, base-yellow, base-orange, base-red, negotiated.
- `is_trading_halted` (boolean): Halted today.
- `figures` (object): Every figure the run read. A step's figure is `step_<n>_<field>`; fixed columns name their unit (`price_rial`, `market_value_billion_toman`). Units for step figures are in `meta.columns`.

`meta` carries `total_rows`, `total_companies`, `steps` (survivors after each step), `sort`, `columns` (key, English title, format, unit, engine_key) and `money_unit` (billion_toman).
- Note: Every page costs the run's price; page through with `cursor` inside 5 minutes, while the run is cached.
- Note: The step params are the same as the site's filter builder; build one there, then copy its body.

Request body example:
```json
{"filters": [{"step": "financial-ratio", "params": {"title_slug": "pe_ttm", "operator": "lt", "value": 8}}], "limit": 2}
```

Example:
```json
{"data": [{"slug": "gardeshgari", "symbol": "وگردش", "name": "بانک گردشگری", "industry": {"slug": "bank", "name": "بانک ها و موسسات اعتباری"}, "board": "farabourse", "is_trading_halted": false, "figures": {"step_1_value": 1.676, "pe_ttm": 1.676, "price_rial": 1730, "market_value_billion_toman": 27584.85, "eps_rial": 1032}}], "next_cursor": "WzJd", "meta": {"updated_at": "2026-11-09T08:30:00+03:30", "total_rows": 168, "total_companies": 168, "steps": [{"step": 1, "group": 1, "family": "financial-ratio", "companies": 168}], "sort": "pe_ttm", "money_unit": "billion_toman"}}
```

### GET /companies/{company}/sales/ (15 credits per 20 rows (each started block), processed, max limit 2000)

Operating revenue by product from the monthly activity letters and the statements, standardised the way the company page shows it: merged product names and consistent units.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `period` (query, string): `1` the monthly activity letter (default); `3`, `6`, `9`, `12` cumulative statement periods. One of: 1, 3, 6, 9, 12. Default `1`.
- `attr` (query, string): One measure, such as `sale_amount`, `sale_price`, `sale_ratio`, `production_amount`, `cost_sale`.
- `from` (query, date): Earliest period end.
- `to` (query, date): Latest period end.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `product_id` (integer): Product id.
- `product` (string): Product name in Persian.
- `product_en` (string, nullable): English name.
- `is_total` (boolean): A sum line (internal, export, total…).
- `income_category` (string): `internal_sale`, `export_sale`, `services_revenues`, `discount`, `return_on_sale`…
- `attr` (string): Which measure the row holds.
- `period_months` (integer): 1, 3, 6, 9 or 12.
- `period_end` (date): Last day of the period.
- `period_end_jalali` (string): Jalali.
- `value` (number, nullable): The figure.
- `unit` (string, nullable): Unit as filed, in Persian (تن، هزار تن، عدد…).
- `money_unit` (string, nullable): `million_rial` for amounts, `rial_per_unit` for rates (`*_ratio`, per one `unit`), `rial`, or null for physical quantities.
- `standardized` (boolean): A standardisation (merge or unit change) touched this row.
- Note: Period 1 is that single month, not year to date. 3/6/9/12 are cumulative, as filed.
- Note: Corrections, forecasts and names a merge folded away are left out, as on the site.

Example:
```json
{"data": [{"product_id": 229, "product": "محصولات سرد", "product_en": "Cold products", "is_total": false, "income_category": "internal_sale", "attr": "sale_price", "period_months": 1, "period_end": "2026-09-22", "period_end_jalali": "1405-06-31", "value": 149710887, "unit": "میلیون ریال", "money_unit": "million_rial", "standardized": false}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/sales/raw/ (10 credits per 20 rows (each started block), raw, max limit 2000)

The same rows as `sales`, with every figure as Codal filed it: where a standardisation changed a row, the original value, unit, product name and category are put back and `standardized` is true.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `period` (query, string): `1` the monthly activity letter (default); `3`, `6`, `9`, `12` cumulative statement periods. One of: 1, 3, 6, 9, 12. Default `1`.
- `attr` (query, string): One measure, such as `sale_amount`, `sale_price`, `sale_ratio`, `production_amount`, `cost_sale`.
- `from` (query, date): Earliest period end.
- `to` (query, date): Latest period end.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `product_id` (integer): Product id.
- `product` (string): Product name in Persian.
- `product_en` (string, nullable): English name.
- `is_total` (boolean): A sum line (internal, export, total…).
- `income_category` (string): `internal_sale`, `export_sale`, `services_revenues`, `discount`, `return_on_sale`…
- `attr` (string): Which measure the row holds.
- `period_months` (integer): 1, 3, 6, 9 or 12.
- `period_end` (date): Last day of the period.
- `period_end_jalali` (string): Jalali.
- `value` (number, nullable): The figure.
- `unit` (string, nullable): Unit as filed, in Persian (تن، هزار تن، عدد…).
- `money_unit` (string, nullable): `million_rial` for amounts, `rial_per_unit` for rates (`*_ratio`, per one `unit`), `rial`, or null for physical quantities.
- `standardized` (boolean): A standardisation (merge or unit change) touched this row.
- Note: A row a merge dropped as a duplicate cannot be shown.

Example:
```json
{"data": [{"product_id": 229, "product": "محصولات سرد", "product_en": "Cold products", "is_total": false, "income_category": "internal_sale", "attr": "sale_amount", "period_months": 1, "period_end": "2026-09-22", "period_end_jalali": "1405-06-31", "value": 138, "unit": "هزار تن", "money_unit": null, "standardized": false}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/commodity-trades/ (10 credits per 20 rows (each started block), raw, max limit 2000)

Every trade of the company's products on the Iran Mercantile Exchange (IME), newest first, as IME published it.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `from` (query, date): Earliest date, inclusive. Jalali (`1404-01-01`) or Gregorian (`2025-03-21`) both work.
- `to` (query, date): Latest date, inclusive, in either calendar.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `id` (string): IME's own trade id.
- `date` (date): Trade date.
- `date_jalali` (string): Jalali.
- `product` (string): Product name.
- `product_en` (string, nullable): English name.
- `product_symbol` (string): IME symbol.
- `income_category` (string): `internal_sale` or `export_sale`.
- `contract_type` (string): `cash`, `credit`, `self` (سلف), `cash_dollar`, `cash_export`…
- `currency` (string): `IRR`, `USD`, `USD_FREE`, `EUR`, `AED`…
- `unit` (string): Trading unit as filed (تن، عدد…).
- `supply` (number): Volume offered, in `unit`.
- `demand` (number): Volume demanded.
- `quantity` (number): Volume traded.
- `base_price` (number): Base offer price.
- `min_price` (number): Lowest price.
- `max_price` (number): Highest price.
- `price` (number): Weighted-average price. Rial per kg for ton-traded rial trades, per item for count-traded; in `currency` per `unit` for currency trades.
- `value_thousand_rial` (number, unit thousand_rial, nullable): Trade value of a rial trade.
- `value_in_currency` (number, nullable): Trade value of a currency trade, in `currency`.
- `delivery_date` (date, nullable): Delivery date.
- `delivery_date_jalali` (string, nullable): The same date in the Jalali calendar.
- `settlement_date` (date, nullable): Settlement date.
- `settlement_date_jalali` (string, nullable): The same date in the Jalali calendar.
- `warehouse` (string, nullable): Delivery warehouse.
- `settlement` (string, nullable): Settlement terms, Persian as filed.
- Note: Offers with no trade are left out. Nothing is converted to rial: this is the raw dataset.

Example:
```json
{"data": [{"id": "r685122", "date": "2026-10-11", "date_jalali": "1405-07-19", "product": "آرگون", "product_en": "Argon", "product_symbol": "MSC-ARGAB-00", "income_category": "internal_sale", "contract_type": "self", "currency": "IRR", "unit": "تن", "supply": 200, "demand": 460, "quantity": 200, "base_price": 321300, "min_price": 621899, "max_price": 648999, "price": 628847, "value_thousand_rial": 125769400, "value_in_currency": null, "delivery_date": "2026-11-09", "settlement_date": null, "warehouse": "انبار کارخانه", "settlement": "نقدی"}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/dividends/ (15 credits per 20 rows (each started block), processed, max limit 100)

One row per fiscal year: the annual meeting, the dividend per share it approved, and when it is paid to shareholders.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `from` (query, date): Earliest fiscal year end.
- `to` (query, date): Latest fiscal year end.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `fiscal_year_end` (date): End of the fiscal year the meeting decides on.
- `fiscal_year_end_jalali` (string): The same date in the Jalali calendar.
- `meeting_date` (date, nullable): Meeting date.
- `meeting_date_jalali` (string, nullable): The same date in the Jalali calendar.
- `meeting_time` (string, nullable): HH:MM, Tehran time.
- `meeting_url` (string, nullable): Codal letter of the invitation.
- `payment_from` (date, nullable): Start of the payment window to individual shareholders.
- `payment_from_jalali` (string, nullable): The same date in the Jalali calendar.
- `payment_to` (date, nullable): End of the window. Equal to `payment_from` when one day is named.
- `payment_to_jalali` (string, nullable): The same date in the Jalali calendar.
- `payment_text` (string, nullable): The schedule letter's own words, Persian.
- `payment_estimated` (boolean): The letter names no single day.
- `schedule_url` (string, nullable): Codal letter of the payment schedule.
- `dividend_per_share_rial` (integer, unit rial_per_share, nullable): Approved dividend per share.
- `days_to_payment` (integer, nullable): Days from the meeting to the payment.
- Note: Only readings we are confident in are served, as on the site.

Example:
```json
{"data": [{"fiscal_year_end": "2026-03-20", "fiscal_year_end_jalali": "1404-12-29", "meeting_date": "2026-07-20", "meeting_time": "09:00", "payment_from": "2026-09-08", "payment_to": "2026-09-08", "payment_text": "۱۴۰۵/۰۶/۱۷", "payment_estimated": false, "dividend_per_share_rial": 55, "days_to_payment": 50}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/letters/ (10 credits per 20 rows (each started block), raw, max limit 500)

The financial-report letters the figures are read from: monthly activity reports and interim and annual statements, with links to the letter, its PDF and Excel export.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `period` (query, string): 1, 3, 6, 9 or 12. One of: 1, 3, 6, 9, 12.
- `from` (query, date): Earliest publish day.
- `to` (query, date): Latest publish day.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `id` (integer): Our id.
- `title` (string): Codal's title.
- `period_months` (integer): 1, 3, 6, 9 or 12.
- `period_end` (date): End of the reported period.
- `period_end_jalali` (string): The same date in the Jalali calendar.
- `published_at` (datetime): ISO 8601 with Tehran offset.
- `published_at_jalali` (string): The same date in the Jalali calendar.
- `is_audited` (boolean): Audited.
- `is_consolidated` (boolean): Consolidated (تلفیقی).
- `is_correction` (boolean): A correction (اصلاحیه).
- `url` (string): The Codal letter.
- `attachment_url` (string, nullable): Attachments.
- `pdf_url` (string, nullable): PDF.
- `excel_url` (string, nullable): Excel export.
- Note: A period filed more than once (unaudited, audited, corrected) shows every filing.

Example:
```json
{"data": [{"id": 92671, "title": "اطلاعات و صورت‌های مالی میاندوره‌ای دوره ۶ ماهه منتهی به ۱۴۰۵/۰۶/۳۱ (حسابرسی نشده)", "period_months": 6, "period_end": "2026-09-22", "published_at": "2026-10-22T14:37:13+03:30", "is_audited": false, "is_consolidated": false, "is_correction": false, "url": "https://www.codal.ir/Reports/Decision.aspx?LetterSerial=…"}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```

### GET /companies/{company}/portfolio/ (20 credits per 20 rows (each started block), processed, max limit 1000)

What a fund or an investment holding owns, one row per investee per monthly report. Without a date you get the newest report. A company that is neither answers an empty list.

Parameters:
- `company` (path, string): The company's `slug` from /companies/, or its symbol (فولاد). Symbols are matched in every spelling Codal and TSETMC use.
- `report_date` (query, date): Exactly this month's report.
- `from` (query, date): Earliest date, inclusive. Jalali (`1404-01-01`) or Gregorian (`2025-03-21`) both work.
- `to` (query, date): Latest date, inclusive, in either calendar.
- `limit` (query, integer): Rows per page. Defaults to one priced block (`per_rows`: 30 for daily trading, 20 for most datasets). A page pays once per started block, so 21 statement rows cost twice. Above the dataset's cap is a 400.
- `cursor` (query, string): The `next_cursor` of the previous page, sent back as it came. Leave it out for the first page.

Fields:
- `portfolio_type` (string): `fund` or `holding`.
- `report_date` (date): Month end reported.
- `report_date_jalali` (string): The same date in the Jalali calendar.
- `letter_url` (string): The Codal letter.
- `letter_tracing_no` (integer): Codal tracing number.
- `name` (string, nullable): Investee as written in the sheet.
- `company` (object, nullable): `{slug, symbol}` of the listed company, if linked.
- `closing_quantity` (number, unit share, nullable): Shares held at month end.
- `closing_cost_rial` (number, unit rial, nullable): Cost at month end.
- `closing_value_rial` (number, unit rial, nullable): Value at month end (net sale value for a fund, market value for a holding).
- `ownership_percent` (number, unit percent, nullable): Holding only: share of the investee owned.
- `segment` (string, nullable): Holding only: `exchange`, `base` or `unlisted`.

A fund row also has opening, bought and sold quantity, cost and amount (`opening_*`, `bought_*`, `sold_*`) and `closing_price_rial`. A holding row has `opening_*`, the month's `change_*`, `capital_rial`, `par_value_rial`, `is_rights` and `cost_per_share_rial`. All money is in rial.
- Note: Values are as filed; only the stock sheet is covered.

Example:
```json
{"data": [{"portfolio_type": "fund", "report_date": "2026-09-22", "letter_tracing_no": 1606387, "name": "آريان كيميا تك", "company": {"slug": "kimia-tech", "symbol": "کیمیاتک"}, "closing_quantity": 1116173, "closing_price_rial": 19450, "closing_cost_rial": 8887705531, "closing_value_rial": 21541749914}], "next_cursor": "…", "meta": {"updated_at": "2026-11-09T08:30:00+03:30"}}
```
