# `POST /screener/ownership`

**Purpose:** Full-library transaction screening. Trade-level conditions plus company-level aggregate gates; returns the matching transactions.

**Request:**

| Field | Type | Default | Notes |
|---|---|---|---|
| `start_date`, `as_of_date` | ISO date | — | Window on `transaction_date`; `as_of_date` also excludes filings submitted after it. `as_of_date` ≤ today; `start_date` ≤ `as_of_date`. |
| `tickers`, `exclude_tickers` | string[] | omitted | ≤ 100 each. |
| `sectors` | string[] | omitted | The 12 canonical buckets ([Industry classification (Pro+)](/documents/coverage.md#industry-classification-pro)). |
| `trade_filters` | object | omitted | See [Filters (`/screener/ownership`)](/documents/post-screenerownership.md#filters-screenerownership). |
| `aggregate_filters` | object[] | omitted | ≤ 8. Company-level gates — see [Filters (`/screener/ownership`)](/documents/post-screenerownership.md#filters-screenerownership). |
| `group_by_ticker` | bool | `true` | `true` → grouped by company. `false` → flat rows. |
| `sort_by` | string | — | Grouped: `ticker`, `txn_count`. Flat: `transaction_date`, `filing_date`, `transaction_value`, `shares`, `price_per_share`, `own_pct_change`, `split_adjusted_shares`, `split_adjusted_price`. |
| `sort_order` | `"desc"` \| `"asc"` | `"desc"` | |
| `page` | int ≥ 1 | 1 | |
| `page_size` | int 1–100 | 50 | Counts transactions in both modes: with `group_by_ticker=true` a page holds that many transactions, however few companies they come from. |
| `detail` | string | `"full"` | `"light"` leaves out each row's `footnote_refs` and `computed.*.fact_refs`, and the `start_date_applied` echo; `"minimal"` also leaves out `accession_number` and `company_id`. See [Response detail](/documents/responses.md#response-detail). |

**Pagination cap:** `page × page_size ≤ 500` (422 above it). When the next page would pass the cap, `has_more` is `false`, `next_page` is `null`, and `_warnings` says the limit was reached.

**Response:**

| Field | Meaning |
|---|---|
| `page`, `page_size`, `has_more`, `next_page` | Pagination. |
| `as_of_date_applied`, `start_date_applied` | Window actually used. |
| `results` | `group_by_ticker=true`. Each entry: `ticker`, `issuer`, `issuer_cik`, `txn_count` (the company's full hit count), `transactions`. |
| `transactions` | `group_by_ticker=false`. Flat rows, each additionally carrying `ticker`, `issuer`, `issuer_cik` and `company_id`. |
| `anomalies_excluded_count` | Transactions excluded by the consistency checks; `0` when `trade_filters.include_anomalies=true`. |
| `credits_used`, `_warnings` | |

Every row carries `accession_number`, `filing_date`, `source_url_prefix`, `insiders`, the [transaction object](/documents/transaction-object.md), and `computed`.

**Errors:** `422 VALIDATION_ERROR`, `402 CREDITS_INSUFFICIENT`, `403 PLAN_TIER_INSUFFICIENT` below Pro, `504 QUERY_TIMEOUT` at the 30-second server timeout (charged).

**Example Request:**

```json
{
  "start_date": "2026-07-01",
  "as_of_date": "2026-09-30",
  "trade_filters": {
    "transaction_code": ["P"],
    "conditions": [
      {
        "field": "transaction_value",
        "op": ">=",
        "value": 1000000
      }
    ]
  },
  "group_by_ticker": false,
  "sort_by": "transaction_value",
  "page_size": 20
}
```

## Filters (`/screener/ownership`)

**`trade_filters`:**

| Field | Type | Notes |
|---|---|---|
| `transaction_code` | string[] | ≤ 20. Unknown codes dropped with a warning. |
| `relationship` | string[] | `is_director`, `is_officer`, `is_ten_percent_owner`, `is_other`. OR semantics. |
| `is_derivative` | bool | |
| `principal_amount_not_shares` | bool | Omit for no filter, `true` for debt-principal rows only, `false` to exclude them. |
| `include_anomalies` | bool | Default `false`. |
| `exclude_likely_merged` | bool | Default `false`. |
| `include_unresolved_amendments` | bool | Default `false`. |
| `conditions` | object[] | ≤ 8. `{field, op, value}`. `field` ∈ `transaction_value`, `price_per_share`, `shares`, `own_pct_change`, `split_adjusted_shares`, `split_adjusted_price`. |

**`aggregate_filters`:** `{metric, op, value}`, `metric` ∈ `value_acquired`, `value_disposed`, `net_value`, `shares_acquired`, `shares_disposed`, `net_shares`, `txn_count`, `txn_count_acquired`, `txn_count_disposed`, `max_txn_value_acquired`, `max_txn_value_disposed`, `distinct_insiders`.

**Operators** (both filter types): `>`, `>=`, `<`, `<=`, `between` (`[lo, hi]`, `lo ≤ hi`).

## Credit formula (`/screener/ownership`)

`POST /screener/ownership` charges per call:

```text title="Formula"
credits = max(65, 200
              − tickerDiscount        // 100 − min(n_tickers, 100); 0 when tickers is omitted
              − shortWindowDiscount   // 40 when the window is ≤ 90 days
              + fullHistorySurcharge  // 80 when start_date is omitted
              + 40 × conditions)      // trade conditions + aggregate filters
```

Range **65–920**. Charged even when the result set is empty, and on `504 QUERY_TIMEOUT`. **No charge** on `422 VALIDATION_ERROR`, `402 CREDITS_INSUFFICIENT`, or `403 PLAN_TIER_INSUFFICIENT` (all rejected before any deduction).
