Skip to main content
Glama

KeyVex

get_insider_transactions

Read-only

Returns executive insider transactions filed on SEC Form 4 — open-market purchases and sales by officers, directors, and 10%-owners of public companies. Each record is one transaction line item from one filing. Use this when the user asks about: insider buying or selling at a specific company, all recent insider activity across the market, transactions by a specific officer, or large insider trades by value. Form 4 is the fastest insider-trade signal in the public record — must be filed within 2 business days of the trade. The reporting_lag_days field tells you how stale a particular disclosure is. Returns BOTH non-derivative rows (direct common-stock buys/sells, RSU vests, grants, gifts, tax-withholding sales) AND derivative rows (option exercises, warrant conversions, RSU/PSU activity). Filter to one or the other with is_derivative; filter to specific transaction codes with transaction_codes. Common transaction codes: P open-market purchase | S open-market sale A grant / award / RSU vest | M exercise of derivative X exercise of in/at-the-money derivative | C conversion of derivative F payment of exercise price or tax with shares | G bona fide gift D disposition to issuer (forced) | I 401(k)/ESPP | V voluntary ⚠ shares and price_per_share are NULLABLE, and null does not mean zero. SEC permits either to be omitted — the price can live in a footnote, and the share count is genuinely undetermined on instruments that convert at a future price (a convertible note settling on a later VWAP). Those filings state a dollar amount instead, so such rows carry total_value with a null shares. Before 2026-08-18 they were dropped from this dataset entirely. Do not do arithmetic on either field without a null check, and do not read a null share count as a trade of nothing — read total_value. Useful filter combos: ⚠ transaction_codes=['P'] IS NOT 'open-market buys'. SEC defines P as 'open market OR PRIVATE purchase', and the code alone says nothing about whether the security is common stock. Verified 2026-08-14: FLUT's code-P rows are $250M of Total Return Swaps (is_derivative=true) and ATTO's are an $8.5M private placement. Both are correctly labelled in transaction_nature and security_title — but a screen filtered on the code alone ranks them top by size. transaction_codes=['P'], is_derivative=false, include_non_open_market=false genuine open-market common-stock buys — the combination you almost always want transaction_codes=['M','X'] option exercises (cash-out trigger) transaction_codes=['A'] grants / RSU vests is_derivative=true all option/RSU/warrant activity is_derivative=false, transaction_type='sell', min_value=1000000 large open-market sells of common stock Optional include_baseline=true: also returns matching Form 3 initial- ownership records (the insider's starting position when they first became an insider) under a baselines field. Use this when you need to know how big a sale is relative to the insider's full position — Form 4 alone shows the delta, Form 3 anchors the baseline. Requires ticker or company_cik to be set. Baseline rows with is_nil_filing=true are 'no securities owned' Form 3s (~half of all filings) — the insider filed but started with ZERO holdings; shares_owned 0 is the position, not missing data. A ticker search also returns rows this issuer FILED UNDER SYMBOLS IT NO LONGER LISTS — renames, filer typos and ADR spellings all split a company's history across symbols. Each row keeps ticker exactly as SEC received it and gains current_ticker when the issuer trades under a different symbol today; the response carries ticker_resolution naming every symbol searched. Separately-listed share classes are NOT merged: GOOG does not return GOOGL. Asking for a RETIRED symbol returns only rows filed under it, because retired symbols get reissued to other companies. Rows found under a retired symbol are checked against the issuer's CIK, so a symbol another company files under today cannot leak its rows in. Coverage: full history on the bulk leg; the live-feed leg is scanned back 180 days, so a rename in the last few days may not be covered yet. data_source SELECTS WHICH BACKING COLLECTION: 'bulk_v2' (DEFAULT as of 2026-05-24) — insider_transactions_v2 collection populated by SEC quarterly bulk Forms 3/4/5 TSV bundles. Deeper history (2006q1 → latest published quarter, ~9.9M rows). ⚠ RECENCY: the bulk dataset ends at the last PUBLISHED quarter (SEC releases it ~2 weeks after quarter end). On simple recency queries (descending sort, no v2-only filters) filings newer than that boundary are AUTO-MERGED from the live daily feed, so the default view stays current — coverage_warning says when this happened. For post-boundary browsing with v2-only filters, query data_source:'legacy' directly. INLINED FOOTNOTES (footnote_refs[] with resolved text on every row), aff10b5one 10b5-1 plan flag, full reporting_owners array, schema_era. Filters: ticker, company_cik, reporting_owner_cik, reporting_owner_name (substring), row_type ('nonderiv'|'deriv'), trans_codes (aka transaction_codes — either spelling works on either data_source), aff10b5one, schema_era ('pre_2023'|'2023_plus'), since/until, sort_by ('transaction_date'|'filing_date'). PLAUSIBILITY FIELDS — WHAT THEY CAN AND CANNOT CONCLUDE: Every row carries price_check and volume_check, and both are ALWAYS non-null: they say whether each check ran, and why not when it did not. Read those first. ⚠ THE RAW MARKET VALUES ARE PAID-PLAN ONLY. price_range_low and price_range_high (bulk rows), daily_range (legacy and live-feed rows) and shares_vs_daily_volume are Tiingo market data, which KeyVex's licence restricts to paid plans. On other plans those keys are OMITTED — not null — and the response carries licensed_fields_withheld naming them. The verdicts computed from them (price_check, price_outside_daily_range, price_fits_date, volume_check, volume_verdict) are served on every plan. ⚠ THE OTHER THREE ARE OFTEN ABSENT OR NULL, AND THIS TEXT USED TO SAY 'every row carries' ALL FOUR, WHICH WAS FALSE. Measured 2026-09-04 by the KeyVex auditor over a 500-row market-wide March sample: price_outside_daily_range key present 500/500, NON-NULL on 178 volume_verdict key present 500/500, NON-NULL on 178 shares_vs_daily_volume key ABSENT on 500/500 The 322 nulls are exactly the rows where volume_check is not 'checked' — so the reason is always available, on the field that says so. shares_vs_daily_volume is stamped only alongside a NON-NORMAL volume verdict, and the same sample contained no non-normal rows, so an ordinary response carries it nowhere. Do not build on its presence. A null here means NOT CHECKED. It never means fine. They were served without definition until 2026-09-01, which is how 'impossible' came to read as a stronger claim than the check supports. volume_verdict compares REPORTED SHARES against that day's recorded volume, and shares_vs_daily_volume is the raw ratio so you can judge for yourself: 'normal' ratio <= 0.25 'outsized' ratio > 0.25 — a large share of the day's tape 'impossible' ratio > 1 — MORE SHARES THAN THE DAY RECORDED. ⚠ 'impossible' means the two numbers cannot both be right, NOT that the trade did not happen. Our volume is one daily bar: it need not include off-exchange or block prints, and a Form 4 may report several days' activity on one date. Treat it as strong evidence of a reporting or data problem worth investigating, not as proof the transaction is fake. null = not judged. Computed only for market claims (codes P and S) — a grant never touched the tape, so a ratio on it would be noise. price_check says whether the price was tested against that day's bar: 'checked' tested; price_outside_daily_range holds the result 'misdated' tested; the price is OUTSIDE that day's bar (see daily_range) — price_outside_daily_range is TRUE — but fits the bar of price_fits_date, within 3 calendar days. Read as: a real fill whose filer wrote the wrong date. KeyVex's own screens treat it as real: its dollar total is kept, it is not vetoed as non-open-market, and co-report resolution never drops it silently. 'no_price_reported' the filer stated no price 'no_positive_price' the filer stated a price of zero or less, so there was nothing to compare. The bar may be present — see volume_check. 'no_verdict_recorded' the day's bar was found (volume_check ran off it) but its high/low were unusable, so the price half is unjudged 'no_daily_bar' no usable bar for that ticker and date ⚠ THE LAST THREE ARE DELIBERATELY DISTINCT. Until 2026-09-04 all three were served as 'no_daily_bar', which asserted a missing bar on rows whose shares_vs_daily_volume — a ratio computable only FROM that day's bar — was served three fields away. Found live by the trading simulation. If you match on 'no_daily_bar', match on all four. volume_check says whether the SHARES-vs-VOLUME check ran, and is now independent of the price half: 'checked' | 'not_a_market_trade' | 'no_daily_bar'. A row can be volume_check 'checked' while price_check is 'no_positive_price' — the bar was there, only the price was not. price_outside_daily_range is true|false|null, and NULL MEANS NOT CHECKED — never 'fine'. A row whose price_check is any value other than 'checked' or 'misdated' has not been vetted on price at all, so do not read its silence as a pass. CLUSTER BUY (every data_source): cluster_buy_insiders_30d = the number of DISTINCT reporting owners (by CIK) with an open-market purchase (code P) in the same ticker in the 30 days ending on this row's transaction_date, this row included; cluster_buy = a code-P row with that count >= 3. Both are NULL — never a guess — on a row that is not a purchase, or when the window cannot be counted; cluster_buy_basis always says which, e.g. 'owner CIK not recorded for trades before 2026-07-01' (live-feed rows before then carry no owner CIK; bulk rows always do). BACKWARD-COMPAT: every v2 row also carries the LEGACY field aliases (disclosure_date, transaction_code, shares, price_per_share, total_value, acquired_disposed, shares_owned_after, officer_name, is_derivative, reporting_lag_days, data_source, sec_filing_url) so callers reading the old field names keep working. The transaction_type field carries the legacy 'buy'|'sell' semantic (synthesized from trans_code + trans_acquired_disp_cd, identical algorithm to the legacy scraper); the v2 nonderiv|deriv discriminator lives at row_type. 'legacy' — insider_trades collection populated by KeyVex's daily EDGAR scraper. Shallower coverage (2022+), no footnotes, no aff10b5one, ~91% fewer filings in the same window than bulk_v2. Rows written since 2026-09-30 also carry reporting_owner_cik (the filing's FIRST reporting owner, 10-digit, the bulk's own rule) and reporting_owner_ciks (every owner on the filing); older legacy rows do not, so the field's absence means 'not recorded', not 'none'. Filters: ticker, company_cik, officer_name, transaction_type (buy|sell), is_derivative, transaction_codes (aka trans_codes), min_value, since/until, sort_by (disclosure_date|transaction_date|total_value). Use this only when you specifically need the legacy doc shape with NO v2-extension fields. SEC-SOURCE DATE CONVENTIONS — read raw values with these in mind: KeyVex preserves SEC's authoritative bytes exactly as published. Two recurring source-data patterns are worth recognizing so agents interpret raw date values correctly: (1) PERPETUAL-INSTRUMENT SENTINEL — exercise_date or expiration_date values of 2050-12-31 / 2050-08-31 ARE SEC's established convention for instruments with no calendar expiration (Deferred Stock Units, certain Non-Qualified Stock Options, Units of Limited Partnership Interest, similar perpetual or condition-vested derivatives). Read these as 'no expiration,' not as literal calendar dates in 2050. This is a fact about SEC's schema, not an inference. (2) ANOMALOUS-YEAR FILER-ENTRY PATTERN — date values with out-of-range year components — e.g., 0012-11-21 or 0025-07-25 (likely 2-digit years entered into a 4-digit field), or 2027-01-25 on a 2026 filing / 2028-03-19 on a 2024 filing (likely single-digit transpositions) — appear to be filer data-entry typos preserved verbatim from SEC's primary filings. KeyVex verified on a stratified spot-check that these values are byte-identical between SEC's primary XML and SEC's bulk extract (22 / 22 matches across all observed pattern faces); the SEC-to-KeyVex transit is faithful. The pattern is ongoing — observed across filings from 2014 through 2026, not legacy-only. Cross-reference filing_date to infer the likely intended year. (3) NUMERIC PRECISION — for data_source='bulk_v2', shares and price_per_share mirror SEC's BULK Form 345 extract, which rounds to 2 decimals (e.g. 474.6, where the primary XML shows 474.598). That rounding is SEC's, in the bulk feed — KeyVex stores the bulk value verbatim (no rounding in the loader). Audit v2 numerics against the bulk extract (the source of record), not the XML primary document, which carries fuller precision. Dates and transaction codes DO match the XML exactly. (4) A DISCLOSURE DATE IS NOT CLOSED WHEN THE DAY ENDS. Filings keep arriving bearing a disclosure_date that has already passed, because SEC accepts them late and there is no cut-off after which a date stops gaining rows. So the SAME disclosure_date window can return MORE rows tomorrow than it did today, and a result cached against that date goes quietly stale — the count does not change, so nothing looks wrong. Observed 2026-08-14: the newest disclosure_date on the tape was still 2026-08-13, yet a row bearing 08-13 was first ingested at 07:01 ET the NEXT morning. A caller working from the previous afternoon's view of 08-13 missed $1.26M of buying in a position it already held. (5) SANITY-CHECK A BIG DOLLAR FIGURE AGAINST VOLUME, IN THIS API. total_value is derived (shares x price) wherever SEC did not file a total, so a filer's unit or decimal slip lands in it. The cheapest test is whether that many shares could plausibly have traded: get_daily_prices(ticker, since, until, include_ohlc=true) -> volume Compare the reported share count to the session's volume. A purchase that is a large multiple of everything that traded is worth a second look before acting on it. Verified examples, 2026-08-14: COE reported 592,320 ordinary shares against 52,418 ADS traded — a 60:1 ADS ratio, not a real $11.8M buy. EVGN reported 460,000 against 326,793 traded (141%) and was FINE — Evogene is dual-listed on NASDAQ and Tel Aviv, so the US tape sees only part of the volume. The check flags what to examine; it does not decide. Both readings beat ranking by total_value and trusting the top. RE-QUERY rather than reusing a prior window. 'Same disclosure date' does not mean 'same rows'. If you need to detect what is NEW since your last look, compare against the row identity you saw before rather than assuming a closed date is settled — and note that a sync timestamp moving is a statement about the JOB, not about the DATA. Pure-publisher posture: KeyVex mirrors SEC's exact bytes, documents these conventions and filer quirks rather than altering them, and never silently 'corrects' a value to KeyVex's guess of what was meant. A customer auditing KeyVex against EDGAR's source of record for each row (the bulk Form 345 extract for v2 rows) will find a byte-for-byte match. MACHINE-READABLE FLAGS — responses include a source_metadata block on rows where the above SEC-source patterns are detected. The block is keyed by field name, with an array of flag strings per field: sec_perpetual_sentinel (assertive — exact-string match on a known SEC sentinel value); anomalous_year_likely_filer_entry (calibrated — year outside the plausible range on transaction_date, exercise_date, expiration_date, or period_of_report; covers filing-pipeline data quality issues across the upstream-actor stack including filer typos, filing-agent default-epoch substitutions, and other cause-classes where the year falls outside any plausible range). Presence is the signal: clean rows have NO source_metadata field at all (not an empty object — the field is omitted entirely). Absence means 'no SEC source quirks detected,' NOT 'certified clean by audit' — agents weigh the difference. The raw source date values are preserved unchanged; the flag block carries KeyVex's labeled interpretation alongside, never replacing.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum records to return. Default 50, max 500.
sinceNoISO date (YYYY-MM-DD). Only records on or after this date, using sort_by as the date field.
untilNoISO date (YYYY-MM-DD). Only records on or before this date.
cursorNoOpaque pagination cursor. To read past `limit`, pass the `next_cursor` value from the previous response VERBATIM; the response omits next_cursor when there is nothing after this page. Keyset-based, not offset-based: the cursor names a position in the sort order, so filings that arrive while you page do not shift or duplicate rows beneath you. Works on BOTH data sources (bulk_v2 since 2026-08-08) — on bulk_v2 it names a position in the MERGED bulk + live-daily-feed stream, and both legs are advanced together. Keep sort_by/sort_order identical across a page sequence — a cursor issued under a different sort is REJECTED rather than silently applied to the wrong ordering. (`offset` and `page` are not supported and are rejected: an offset into this result is not an offset into either underlying source, and on the legacy store it would bill for every row it skipped.)
tickerNoStock symbol filter, e.g. 'AAPL'. Case-insensitive.
sort_byNoField used for ordering and for the since/until date filters. Default: disclosure_date. Validity differs by data_source: 'transaction_date' works on BOTH; 'disclosure_date' works on both (on the default bulk_v2 it maps to filing_date); 'total_value' is LEGACY-ONLY — bulk_v2 rejects it, so to rank by trade size on bulk_v2 use min_amount to filter and sort by transaction_date instead.
row_typeNobulk_v2 only. Source-table discriminator: 'nonderiv' for NONDERIV_TRANS rows (direct common-stock activity), 'deriv' for DERIV_TRANS rows (options/RSUs/warrants). For legacy data, use is_derivative instead.
min_valueNoFilter to trades with total_value >= this amount (USD). Use to focus on large trades. Works on BOTH data_sources. On bulk_v2 the value is DERIVED per row exactly as the returned `total_value` field is — TRANS_TOTAL_VALUE where the SEC populated it (derivative rows), otherwise shares × price_per_share — so the filter and the field always agree. Rows carrying neither (no value and no shares/price) are EXCLUDED, since an unknown value cannot be shown to clear the threshold; that matches legacy — pass include_unpriced=true to keep them, and the response carries `min_value_note` saying so whenever min_value is set. Note this is a post-fetch filter on bulk_v2, so a very high threshold may return fewer rows than `limit` with has_more=true.
aff10b5oneNobulk_v2 only. 10b5-1 trading-plan flag. '1' = plan adopted, '0' = no plan, '' = filer left the box blank (most common in 2023q1+ era), 'NOT_TRACKED' = pre-2023 era where the column did not exist on the SEC form. Filers often leave the box blank but disclose the plan in narrative footnotes — check footnote_refs[] for trans_code annotations.
schema_eraNobulk_v2 only. Form-version era. 'pre_2023' = filings made 2006q1 through 2022q4 (no AFF10B5ONE column). '2023_plus' = filings made 2023q1 onward (AFF10B5ONE column present, matches SEC Rule 10b5-1 amendment compliance date). Driven by FILING-quarter, not transaction_date — a late 2024 filing of an old 2009 trade still gets schema_era=2023_plus.
sort_orderNoDefault: desc (most recent / largest first).
company_cikNoSEC CIK number (10-digit, padded with leading zeros). Alternative to ticker when known.
data_sourceNoWhich backing collection to query. 'bulk_v2' (DEFAULT as of 2026-05-24) = SEC quarterly bulk dataset, 2006q1 → the last PUBLISHED SEC quarter (SEC releases it ~2 weeks after quarter end; the live boundary is stated in coverage_warning). Footnote_refs[] inlined, aff10b5one present, full reporting_owners array, deeper coverage. Rows carry legacy field aliases for backward compat. On simple recency queries (descending, no v2-only filters) filings NEWER than the bulk boundary are automatically merged in from the live daily feed, so the default view stays current. 'legacy' = daily EDGAR scraper output (insider_trades collection, no footnotes, no 10b5-1 flag). Its history runs from 2016 but is NOT continuous: measured 2026-08-10 against SEC's own bulk copy, legacy holds ~2.1% of 2022, ~0.3% of 2023 and ~4.4% of 2025, and is complete for 2016-2021, 2024 and 2026. Those are gaps in the scraper's history, not in SEC's data, and responses covering them carry a coverage_warning. (An earlier version of this description said '2022+ only', which was the inverse of the truth.) Use 'legacy' ONLY when you specifically need the legacy document shape, or to browse post-boundary filings with filters the auto-merge can't map. bulk_v2 (the default) is authoritative for coverage.
trans_codesNoOR-filter on raw SEC trans_code values (P, S, A, M, X, C, F, G, D, I, V, etc.). The v2 spelling of `transaction_codes` — same semantics, same codes; either spelling is accepted on either data_source and passing both with different values is rejected. Max 30 codes.
officer_nameNoFull or partial officer name; case-insensitive substring match. Works on BOTH data_sources — `reporting_owner_name` is the same filter under its v2 name, and either spelling is accepted on either source; passing both with different values is rejected.
is_derivativeNoFilter to derivative rows (options, RSUs, warrants, convertibles) when true, or non-derivative common-stock rows when false. Omit to see both. Works on BOTH data_sources — on bulk_v2 it maps to row_type ('deriv'/'nonderiv'); passing is_derivative and a conflicting row_type is rejected.
include_baselineNoWhen true, the response includes matching Form 3 initial-ownership records under a `baselines` field — lets you anchor Form 4 deltas to the insider's starting position. Requires ticker or company_cik. Default false. Works on BOTH data_sources (bulk_v2 accepted-but-ignored it until 2026-08-05).
include_unpricedNoOnly meaningful alongside min_value. By default a row that states NO price (price_check='no_price_reported') has no derivable total_value, cannot be shown to clear the threshold, and is EXCLUDED — correct, but it used to be silent, and 'cannot be shown to clear the threshold' is not 'is below the threshold'. Measured 2026-09-09 at roughly 3 rows in 10,000, every one a filing where the insider genuinely stated no price rather than a data defect. Pass true to keep those rows in a min_value-filtered result; their total_value is null, so judge them on shares. Default false, which is exactly today's behaviour. bulk_v2 only: passing it with data_source='legacy' is REJECTED rather than ignored, because a filter flag that silently does nothing is worse than one that is unavailable. Passing it without min_value is also rejected, for the same reason — nothing would be being excluded for it to re-admit.
merge_co_reportsNoDEFAULT TRUE — leave it alone unless you specifically want raw filings. When shares are held through a fund, SEC requires the holding entity, its general partner AND the individual with investment control to each file their own Form 4 for the SAME purchase. By default those filings are merged into one row, so share counts and dollar totals are what actually traded; the row's officer_name names every filer and co_reported_accessions lists every accession. Set false to get one row per filing instead — useful for filing-level audit, but totals then double-count the transaction once per reporting person.
transaction_typeNoFilter by direction. 'buy' means PURCHASED, not merely acquired: setting this parameter also switches include_non_open_market to false by default, so RSU vests, option exercises and other grants (transaction_nature=EQUITY_COMP) and gifts/transfers/equity swaps (NON_OPEN_MARKET_TRANSFER) are EXCLUDED. Without that default a 'buy' screen is mostly vesting schedules — measured 2026-08-11 at 83% EQUITY_COMP, with a $6.3bn nano-cap grant topping the dollar-sorted results. ⚠ include_non_open_market=true does NOT widen a direction-filtered query, and this description used to say it did ('pass it to get the full ACQUIRED superset, grants included') — corrected 2026-08-27 after the promise was measured against the behaviour. A direction is only ASSERTED for a genuine market trade: since 2026-08-17 the field is null for anything whose transaction_nature is not OPEN_MARKET, and for derivatives, because equity compensation is not buying (a Kyndryl new-hire grant had been ranking as $5.2M of insider buying at a discount to the market price). A row with no direction cannot match transaction_type=buy whatever include_non_open_market says. To see grants and transfers, OMIT transaction_type and filter on transaction_nature yourself — include_non_open_market widens exactly as documented there. Rows whose nature cannot be classified (INSUFFICIENT_DATA) always pass through where no direction filter is set, and are counted in unclassifiable_records_retained — unclassified is not the same as excluded. Works on BOTH data_sources. For legacy, this filters the stored field directly. For bulk_v2 (default), the field is derived per row from trans_code + trans_acquired_disp_cd (P→buy, S→sell, acqDisp=A→buy, acqDisp=D→sell, fallback A/M/X/C/I→buy else sell — same algorithm legacy uses); the v2 path pages through Firestore until enough matches are found and reports has_more accurately on the filtered set.
transaction_codesNoOR-filter on raw SEC transaction codes. Common picks: ['P'] open-market buys; ['S','F'] sells + tax-withholding; ['M','X'] option exercises; ['A'] grants/RSU vests; ['G'] gifts. Max 30 codes. Works on BOTH data_sources — `trans_codes` is the same filter under its v2 name, and either spelling is accepted on either source; passing both with different values is rejected.
reporting_owner_cikNoReporting owner CIK (10-digit, zero-padded). bulk_v2 only — legacy uses officer_name substring instead.
reporting_owner_nameNoReporting owner name substring (case-insensitive). bulk_v2 only — legacy uses officer_name instead. IMPORTANT: name is matched client-side over a recent window, so it must be ANCHORED by ticker, company_cik, or reporting_owner_cik to search the full history — a name on its own only scans the most recent filings and can miss older trades (the response carries a coverage_warning when used unanchored).
include_non_open_marketNoPhase A v0.52.0 (2026-05-24): controls whether NON-MARKET events appear in the result. When false (the honest default for direction queries), the result keeps ONLY OPEN_MARKET rows (transaction_nature='OPEN_MARKET') plus INSUFFICIENT_DATA rows (passthrough — unclassified is not the same as confirmed-non-market, never silently dropped). It excludes BOTH NON_OPEN_MARKET_TRANSFER (gifts G, tax-withhold F, disposition-to-issuer D, will/inheritance W, voting-trust Z, tender U) AND EQUITY_COMP (grants A, exercises M/X/O, 401k/ESPP I, conversions C) — neither is a true open-market trade. Honest-by-default: when transaction_type='buy'|'sell' is set, defaults to FALSE; pass true to opt back in and see all natures. When transaction_type is NOT set, defaults to TRUE (returns everything, honestly tagged); pass false for a clean OPEN_MARKET+INSUFFICIENT_DATA view. The transaction_type field on each row is never mutated by this filter. The response envelope carries `unclassifiable_records_retained: N` when any INSUFFICIENT_DATA rows passed through, so the caller knows N of the returned rows couldn't be classified.

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false and openWorldHint=true, so the safety profile is covered. The description goes well beyond that, disclosing coverage boundaries, license-withheld fields, nullable-value traps, and recency auto-merge behaviour — but much of this is framed as historical corrections ('this text used to say... which was false'), which reads as an audit changelog rather than invocation-relevant behaviour. Substantive transparency is high, but the signal-to-noise is diluted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded, but the body runs to many screen-lengths and is heavily padded with version-dated changelog entries, audit measurements and self-corrections ('measured 2026-09-04 by the KeyVex auditor...', 'this text used to say...'). For an agent selecting and calling a tool, the vast majority of this text does not change behaviour and crowds out the operative guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 24-parameter, zero-required, no-output-schema tool spanning two backing datasets, the definition covers selection, filtering, null semantics, pagination and source-of-truth caveats. Nothing an agent needs in order to call it correctly is missing, even if it is delivered verbosely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, and the schema already documents every filter thoroughly. The description earns above baseline by adding meaning the schema cannot: the P-is-not-open-market trap with named counterexamples, the interaction between transaction_type and include_non_open_market, and the min_value/include_unpriced coupling.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Returns executive insider transactions filed on SEC Form 4') and immediately narrows scope to open-market purchases and sales by officers, directors and 10%-owners. It further distinguishes itself from sibling insider tools by naming the row granularity ('one transaction line item from one filing') and the derivative/non-derivative split.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when the user asks about' enumeration covers the main intents, and the 'Useful filter combos' block names the exact parameter sets to run per intent. The data_source section states when to prefer 'legacy' versus the default 'bulk_v2', including the exclusion condition ('only when you specifically need the legacy doc shape').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources