From e77dd092eb7d9a36e41b4932221ba94ee00e02b1 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:58:39 +0000 Subject: [PATCH] SDK regeneration --- .fern/metadata.json | 7 +- .github/workflows/ci.yml | 11 +- README.md | 62 +- poetry.lock | 434 +- pyproject.toml | 6 +- reference.md | 4349 ++++++++++++----- src/runcaptain/__init__.py | 427 +- src/runcaptain/client.py | 26 +- src/runcaptain/collections/client.py | 74 +- src/runcaptain/collections/raw_client.py | 118 +- src/runcaptain/companies/__init__.py | 117 + src/runcaptain/companies/client.py | 532 +- src/runcaptain/companies/raw_client.py | 902 ++-- src/runcaptain/companies/types/__init__.py | 123 + .../companies_active_investors_response.py | 37 + ...ctive_investors_response_investors_item.py | 37 + .../companies/types/companies_bio_response.py | 76 + .../companies_bio_response_headquarters.py | 21 + .../companies_bio_response_social_profiles.py | 21 + .../types/companies_deals_response.py | 35 + .../companies_deals_response_deals_item.py | 52 + ...ompanies_debt_financing_recent_response.py | 30 + ...es_debt_financing_recent_response_round.py | 46 + .../companies_financials_recent_response.py | 64 + .../types/companies_financials_response.py | 64 + .../companies_financing_recent_response.py | 58 + ...s_financing_recent_response_rounds_item.py | 42 + ...ancing_recent_response_web_sources_item.py | 32 + .../types/companies_full_response.py | 155 + ..._full_response_affiliated_entities_item.py | 20 + ...nies_full_response_funding_details_item.py | 22 + .../companies_full_response_headquarters.py | 21 + .../types/companies_full_response_location.py | 27 + ...companies_full_response_social_profiles.py | 25 + .../types/companies_search_response.py | 43 + .../companies_search_response_results_item.py | 82 + ...mpanies_service_providers_deal_response.py | 39 + ...rs_deal_response_service_providers_item.py | 32 + .../companies_service_providers_response.py | 39 + ...oviders_response_service_providers_item.py | 32 + .../types/companies_similar_response.py | 30 + ...anies_similar_response_competitors_item.py | 32 + src/runcaptain/core/__init__.py | 9 +- src/runcaptain/core/client_wrapper.py | 4 +- src/runcaptain/core/datetime_utils.py | 42 + src/runcaptain/core/parse_error.py | 36 + src/runcaptain/core/pydantic_utilities.py | 83 +- src/runcaptain/credit_analysis/__init__.py | 63 + src/runcaptain/credit_analysis/client.py | 2073 +++++++- src/runcaptain/credit_analysis/raw_client.py | 2543 +++++++++- .../credit_analysis/types/__init__.py | 65 + ...t_analysis_bdc_search_request_seniority.py | 7 + ..._analysis_funds_search_request_strategy.py | 7 + .../credit_analysis_news_bulk_response.py | 24 + ...nalysis_news_bulk_response_results_item.py | 27 + .../credit_analysis_news_detail_response.py | 52 + .../credit_analysis_news_recent_response.py | 29 + ...lysis_news_recent_response_results_item.py | 47 + .../credit_analysis_news_search_response.py | 34 + ...lysis_news_search_response_results_item.py | 47 + ...edit_analysis_sba_search_request_status.py | 5 + src/runcaptain/datasets/client.py | 213 +- src/runcaptain/datasets/raw_client.py | 319 +- src/runcaptain/deals/__init__.py | 69 + src/runcaptain/deals/client.py | 315 +- src/runcaptain/deals/raw_client.py | 585 +-- src/runcaptain/deals/types/__init__.py | 73 + .../deals/types/deals_bio_response.py | 83 + .../types/deals_debt_lenders_response.py | 41 + ...eals_debt_lenders_response_lenders_item.py | 32 + .../deals/types/deals_investors_response.py | 34 + ...ls_investors_response_participants_item.py | 37 + .../deals/types/deals_search_response.py | 43 + .../deals_search_response_results_item.py | 47 + .../types/deals_service_providers_response.py | 36 + ...oviders_response_service_providers_item.py | 32 + .../deals/types/deals_stock_info_response.py | 35 + .../deals/types/deals_valuation_response.py | 29 + .../types/deals_valuation_response_terms.py | 47 + src/runcaptain/errors/__init__.py | 3 + .../errors/unprocessable_entity_error.py | 11 + src/runcaptain/fundamentals/__init__.py | 4 - src/runcaptain/funds/__init__.py | 54 + src/runcaptain/funds/client.py | 431 +- src/runcaptain/funds/raw_client.py | 715 +-- src/runcaptain/funds/types/__init__.py | 56 + .../funds_active_investments_response.py | 36 + ...e_investments_response_investments_item.py | 42 + .../funds/types/funds_bio_response.py | 49 + .../funds/types/funds_bio_response_data.py | 47 + .../funds/types/funds_search_response.py | 38 + .../funds_search_response_results_item.py | 44 + ...s_search_response_results_item_location.py | 21 + src/runcaptain/general/__init__.py | 72 + src/runcaptain/general/client.py | 287 +- src/runcaptain/general/raw_client.py | 427 +- src/runcaptain/general/types/__init__.py | 74 + .../general_entity_affiliates_response.py | 29 + ...ity_affiliates_response_affiliates_item.py | 37 + .../general_entity_locations_response.py | 29 + ...ntity_locations_response_locations_item.py | 42 + .../types/general_entity_news_response.py | 29 + .../general_entity_news_response_news_item.py | 42 + .../types/general_entity_people_response.py | 29 + ...eral_entity_people_response_people_item.py | 37 + .../general_search_request_entity_type.py | 7 + .../general/types/general_search_response.py | 38 + .../general_search_response_results_item.py | 47 + .../types/general_search_shared_response.py | 38 + ...ral_search_shared_response_results_item.py | 47 + src/runcaptain/indexing/__init__.py | 3 + src/runcaptain/indexing/client.py | 863 +++- src/runcaptain/indexing/raw_client.py | 1039 +++- src/runcaptain/indexing/types/__init__.py | 3 + .../index_file_v2request_processing_type.py | 5 + src/runcaptain/investors/__init__.py | 84 + src/runcaptain/investors/client.py | 307 +- src/runcaptain/investors/raw_client.py | 537 +- src/runcaptain/investors/types/__init__.py | 86 + .../investors_active_investments_response.py | 39 + ...ive_investments_response_portfolio_item.py | 47 + .../investors/types/investors_bio_response.py | 97 + .../investors_bio_response_headquarters.py | 21 + .../investors_bio_response_investor_type.py | 32 + .../types/investors_board_seats_response.py | 34 + ...s_board_seats_response_board_seats_item.py | 37 + .../types/investors_funds_latest_response.py | 45 + ...estors_funds_latest_response_funds_item.py | 42 + ...stors_funds_latest_response_latest_fund.py | 27 + .../types/investors_funds_response.py | 39 + .../investors_funds_response_funds_item.py | 42 + .../types/investors_preferences_response.py | 52 + .../types/investors_search_response.py | 43 + .../investors_search_response_results_item.py | 47 + ...vestors_service_providers_deal_response.py | 33 + .../investors_service_providers_response.py | 33 + src/runcaptain/jobs/client.py | 125 +- src/runcaptain/jobs/raw_client.py | 194 +- src/runcaptain/limited_partners/__init__.py | 48 + src/runcaptain/limited_partners/client.py | 248 +- src/runcaptain/limited_partners/raw_client.py | 460 +- .../limited_partners/types/__init__.py | 50 + .../types/lps_bio_response.py | 59 + .../types/lps_bio_response_data.py | 42 + .../types/lps_search_response.py | 43 + .../types/lps_search_response_results_item.py | 58 + ...s_search_response_results_item_location.py | 25 + src/runcaptain/patents/__init__.py | 48 + src/runcaptain/patents/client.py | 89 +- src/runcaptain/patents/raw_client.py | 127 +- src/runcaptain/patents/types/__init__.py | 50 + .../types/patents_get_by_id_response.py | 62 + .../types/patents_get_file_response.py | 36 + .../patents/types/patents_search_response.py | 54 + .../patents_search_response_patents_item.py | 43 + .../patents_search_response_results_item.py | 67 + src/runcaptain/people/__init__.py | 48 + src/runcaptain/people/client.py | 334 +- src/runcaptain/people/raw_client.py | 478 +- src/runcaptain/people/types/__init__.py | 50 + .../people/types/people_bio_response.py | 116 + .../people_bio_response_current_company.py | 37 + .../types/people_bio_response_location.py | 21 + .../people/types/people_search_response.py | 48 + .../people_search_response_results_item.py | 62 + src/runcaptain/query/client.py | 312 +- src/runcaptain/query/raw_client.py | 378 +- src/runcaptain/sandbox_data/__init__.py | 61 + .../{fundamentals => sandbox_data}/client.py | 83 +- .../raw_client.py | 95 +- src/runcaptain/sandbox_data/types/__init__.py | 59 + ...ndamentals_lookup_table_values_response.py | 33 + ...ookup_table_values_response_values_item.py | 27 + .../fundamentals_lookup_tables_response.py | 23 + ...tals_lookup_tables_response_tables_item.py | 32 + .../types/fundamentals_sandbox_response.py | 35 + ...mentals_sandbox_response_companies_item.py | 21 + ...mentals_sandbox_response_investors_item.py | 20 + ...ndamentals_sandbox_response_people_item.py | 20 + src/runcaptain/service_providers/__init__.py | 69 + src/runcaptain/service_providers/client.py | 188 +- .../service_providers/raw_client.py | 342 +- .../service_providers/types/__init__.py | 73 + .../types/service_providers_bio_response.py | 49 + .../service_providers_companies_response.py | 29 + ...iders_companies_response_companies_item.py | 32 + .../types/service_providers_deals_response.py | 29 + ...ice_providers_deals_response_deals_item.py | 37 + .../types/service_providers_funds_response.py | 29 + ...ice_providers_funds_response_funds_item.py | 32 + .../service_providers_investors_response.py | 29 + ...iders_investors_response_investors_item.py | 32 + .../service_providers_search_response.py | 58 + ..._providers_search_response_results_item.py | 78 + ...s_search_response_results_item_location.py | 25 + src/runcaptain/types/__init__.py | 27 +- src/runcaptain/types/conflict_error_body.py | 19 + src/runcaptain/types/dataset_search_result.py | 5 + .../types/job_cancel_response_v2.py | 4 + .../types/job_rollback_response_v2.py | 42 + .../types/not_implemented_error_body.py | 35 + .../not_implemented_error_body_details.py | 27 + src/runcaptain/types/query_response_v2.py | 8 +- src/runcaptain/types/relevant_document_v2.py | 41 - .../types/scientific_ask_response.py | 48 + src/runcaptain/types/scientific_source.py | 39 + src/runcaptain/types/search_result.py | 9 +- .../validate_parsing_script_response_v2.py | 33 + ...te_parsing_script_response_v2error_type.py | 5 + 209 files changed, 20888 insertions(+), 7074 deletions(-) create mode 100644 src/runcaptain/companies/types/__init__.py create mode 100644 src/runcaptain/companies/types/companies_active_investors_response.py create mode 100644 src/runcaptain/companies/types/companies_active_investors_response_investors_item.py create mode 100644 src/runcaptain/companies/types/companies_bio_response.py create mode 100644 src/runcaptain/companies/types/companies_bio_response_headquarters.py create mode 100644 src/runcaptain/companies/types/companies_bio_response_social_profiles.py create mode 100644 src/runcaptain/companies/types/companies_deals_response.py create mode 100644 src/runcaptain/companies/types/companies_deals_response_deals_item.py create mode 100644 src/runcaptain/companies/types/companies_debt_financing_recent_response.py create mode 100644 src/runcaptain/companies/types/companies_debt_financing_recent_response_round.py create mode 100644 src/runcaptain/companies/types/companies_financials_recent_response.py create mode 100644 src/runcaptain/companies/types/companies_financials_response.py create mode 100644 src/runcaptain/companies/types/companies_financing_recent_response.py create mode 100644 src/runcaptain/companies/types/companies_financing_recent_response_rounds_item.py create mode 100644 src/runcaptain/companies/types/companies_financing_recent_response_web_sources_item.py create mode 100644 src/runcaptain/companies/types/companies_full_response.py create mode 100644 src/runcaptain/companies/types/companies_full_response_affiliated_entities_item.py create mode 100644 src/runcaptain/companies/types/companies_full_response_funding_details_item.py create mode 100644 src/runcaptain/companies/types/companies_full_response_headquarters.py create mode 100644 src/runcaptain/companies/types/companies_full_response_location.py create mode 100644 src/runcaptain/companies/types/companies_full_response_social_profiles.py create mode 100644 src/runcaptain/companies/types/companies_search_response.py create mode 100644 src/runcaptain/companies/types/companies_search_response_results_item.py create mode 100644 src/runcaptain/companies/types/companies_service_providers_deal_response.py create mode 100644 src/runcaptain/companies/types/companies_service_providers_deal_response_service_providers_item.py create mode 100644 src/runcaptain/companies/types/companies_service_providers_response.py create mode 100644 src/runcaptain/companies/types/companies_service_providers_response_service_providers_item.py create mode 100644 src/runcaptain/companies/types/companies_similar_response.py create mode 100644 src/runcaptain/companies/types/companies_similar_response_competitors_item.py create mode 100644 src/runcaptain/core/parse_error.py create mode 100644 src/runcaptain/credit_analysis/types/__init__.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_bdc_search_request_seniority.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_funds_search_request_strategy.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response_results_item.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_detail_response.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response_results_item.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_search_response.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_news_search_response_results_item.py create mode 100644 src/runcaptain/credit_analysis/types/credit_analysis_sba_search_request_status.py create mode 100644 src/runcaptain/deals/types/__init__.py create mode 100644 src/runcaptain/deals/types/deals_bio_response.py create mode 100644 src/runcaptain/deals/types/deals_debt_lenders_response.py create mode 100644 src/runcaptain/deals/types/deals_debt_lenders_response_lenders_item.py create mode 100644 src/runcaptain/deals/types/deals_investors_response.py create mode 100644 src/runcaptain/deals/types/deals_investors_response_participants_item.py create mode 100644 src/runcaptain/deals/types/deals_search_response.py create mode 100644 src/runcaptain/deals/types/deals_search_response_results_item.py create mode 100644 src/runcaptain/deals/types/deals_service_providers_response.py create mode 100644 src/runcaptain/deals/types/deals_service_providers_response_service_providers_item.py create mode 100644 src/runcaptain/deals/types/deals_stock_info_response.py create mode 100644 src/runcaptain/deals/types/deals_valuation_response.py create mode 100644 src/runcaptain/deals/types/deals_valuation_response_terms.py create mode 100644 src/runcaptain/errors/unprocessable_entity_error.py delete mode 100644 src/runcaptain/fundamentals/__init__.py create mode 100644 src/runcaptain/funds/types/__init__.py create mode 100644 src/runcaptain/funds/types/funds_active_investments_response.py create mode 100644 src/runcaptain/funds/types/funds_active_investments_response_investments_item.py create mode 100644 src/runcaptain/funds/types/funds_bio_response.py create mode 100644 src/runcaptain/funds/types/funds_bio_response_data.py create mode 100644 src/runcaptain/funds/types/funds_search_response.py create mode 100644 src/runcaptain/funds/types/funds_search_response_results_item.py create mode 100644 src/runcaptain/funds/types/funds_search_response_results_item_location.py create mode 100644 src/runcaptain/general/types/__init__.py create mode 100644 src/runcaptain/general/types/general_entity_affiliates_response.py create mode 100644 src/runcaptain/general/types/general_entity_affiliates_response_affiliates_item.py create mode 100644 src/runcaptain/general/types/general_entity_locations_response.py create mode 100644 src/runcaptain/general/types/general_entity_locations_response_locations_item.py create mode 100644 src/runcaptain/general/types/general_entity_news_response.py create mode 100644 src/runcaptain/general/types/general_entity_news_response_news_item.py create mode 100644 src/runcaptain/general/types/general_entity_people_response.py create mode 100644 src/runcaptain/general/types/general_entity_people_response_people_item.py create mode 100644 src/runcaptain/general/types/general_search_request_entity_type.py create mode 100644 src/runcaptain/general/types/general_search_response.py create mode 100644 src/runcaptain/general/types/general_search_response_results_item.py create mode 100644 src/runcaptain/general/types/general_search_shared_response.py create mode 100644 src/runcaptain/general/types/general_search_shared_response_results_item.py create mode 100644 src/runcaptain/indexing/types/index_file_v2request_processing_type.py create mode 100644 src/runcaptain/investors/types/__init__.py create mode 100644 src/runcaptain/investors/types/investors_active_investments_response.py create mode 100644 src/runcaptain/investors/types/investors_active_investments_response_portfolio_item.py create mode 100644 src/runcaptain/investors/types/investors_bio_response.py create mode 100644 src/runcaptain/investors/types/investors_bio_response_headquarters.py create mode 100644 src/runcaptain/investors/types/investors_bio_response_investor_type.py create mode 100644 src/runcaptain/investors/types/investors_board_seats_response.py create mode 100644 src/runcaptain/investors/types/investors_board_seats_response_board_seats_item.py create mode 100644 src/runcaptain/investors/types/investors_funds_latest_response.py create mode 100644 src/runcaptain/investors/types/investors_funds_latest_response_funds_item.py create mode 100644 src/runcaptain/investors/types/investors_funds_latest_response_latest_fund.py create mode 100644 src/runcaptain/investors/types/investors_funds_response.py create mode 100644 src/runcaptain/investors/types/investors_funds_response_funds_item.py create mode 100644 src/runcaptain/investors/types/investors_preferences_response.py create mode 100644 src/runcaptain/investors/types/investors_search_response.py create mode 100644 src/runcaptain/investors/types/investors_search_response_results_item.py create mode 100644 src/runcaptain/investors/types/investors_service_providers_deal_response.py create mode 100644 src/runcaptain/investors/types/investors_service_providers_response.py create mode 100644 src/runcaptain/limited_partners/types/__init__.py create mode 100644 src/runcaptain/limited_partners/types/lps_bio_response.py create mode 100644 src/runcaptain/limited_partners/types/lps_bio_response_data.py create mode 100644 src/runcaptain/limited_partners/types/lps_search_response.py create mode 100644 src/runcaptain/limited_partners/types/lps_search_response_results_item.py create mode 100644 src/runcaptain/limited_partners/types/lps_search_response_results_item_location.py create mode 100644 src/runcaptain/patents/types/__init__.py create mode 100644 src/runcaptain/patents/types/patents_get_by_id_response.py create mode 100644 src/runcaptain/patents/types/patents_get_file_response.py create mode 100644 src/runcaptain/patents/types/patents_search_response.py create mode 100644 src/runcaptain/patents/types/patents_search_response_patents_item.py create mode 100644 src/runcaptain/patents/types/patents_search_response_results_item.py create mode 100644 src/runcaptain/people/types/__init__.py create mode 100644 src/runcaptain/people/types/people_bio_response.py create mode 100644 src/runcaptain/people/types/people_bio_response_current_company.py create mode 100644 src/runcaptain/people/types/people_bio_response_location.py create mode 100644 src/runcaptain/people/types/people_search_response.py create mode 100644 src/runcaptain/people/types/people_search_response_results_item.py create mode 100644 src/runcaptain/sandbox_data/__init__.py rename src/runcaptain/{fundamentals => sandbox_data}/client.py (64%) rename src/runcaptain/{fundamentals => sandbox_data}/raw_client.py (77%) create mode 100644 src/runcaptain/sandbox_data/types/__init__.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response_values_item.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response_tables_item.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_sandbox_response.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_companies_item.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_investors_item.py create mode 100644 src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_people_item.py create mode 100644 src/runcaptain/service_providers/types/__init__.py create mode 100644 src/runcaptain/service_providers/types/service_providers_bio_response.py create mode 100644 src/runcaptain/service_providers/types/service_providers_companies_response.py create mode 100644 src/runcaptain/service_providers/types/service_providers_companies_response_companies_item.py create mode 100644 src/runcaptain/service_providers/types/service_providers_deals_response.py create mode 100644 src/runcaptain/service_providers/types/service_providers_deals_response_deals_item.py create mode 100644 src/runcaptain/service_providers/types/service_providers_funds_response.py create mode 100644 src/runcaptain/service_providers/types/service_providers_funds_response_funds_item.py create mode 100644 src/runcaptain/service_providers/types/service_providers_investors_response.py create mode 100644 src/runcaptain/service_providers/types/service_providers_investors_response_investors_item.py create mode 100644 src/runcaptain/service_providers/types/service_providers_search_response.py create mode 100644 src/runcaptain/service_providers/types/service_providers_search_response_results_item.py create mode 100644 src/runcaptain/service_providers/types/service_providers_search_response_results_item_location.py create mode 100644 src/runcaptain/types/conflict_error_body.py create mode 100644 src/runcaptain/types/job_rollback_response_v2.py create mode 100644 src/runcaptain/types/not_implemented_error_body.py create mode 100644 src/runcaptain/types/not_implemented_error_body_details.py delete mode 100644 src/runcaptain/types/relevant_document_v2.py create mode 100644 src/runcaptain/types/scientific_ask_response.py create mode 100644 src/runcaptain/types/scientific_source.py create mode 100644 src/runcaptain/types/validate_parsing_script_response_v2.py create mode 100644 src/runcaptain/types/validate_parsing_script_response_v2error_type.py diff --git a/.fern/metadata.json b/.fern/metadata.json index 795f3da..0a2693e 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,9 +1,10 @@ { - "cliVersion": "4.13.0", + "cliVersion": "4.78.0", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "4.59.4", + "generatorVersion": "5.0.0", "generatorConfig": { "client_class_name": "Captain" }, - "sdkVersion": "0.1.1" + "originGitCommit": "773c74e2dd953382ea51bbcc2300f803128e5de5", + "sdkVersion": "0.1.2" } \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4588ce..74af457 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,10 @@ name: ci on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: compile: runs-on: ubuntu-latest @@ -9,7 +14,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -25,7 +30,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -45,7 +50,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 diff --git a/README.md b/README.md index be179fe..986effb 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,19 @@ Instantiate and use the client with the following: from runcaptain import Captain client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", ) -response = client.query.collection_v2stream( - collection_name="collection_name", - query="query", + +client.query.collection_v2( + collection_name="my_documents", + query="What are the key terms in the contract?", + inference=True, + stream=True, + rerank=True, + top_k=10, + include_bbox=False, ) -for chunk in response.data: - yield chunk ``` ## Async Client @@ -59,18 +63,21 @@ import asyncio from runcaptain import AsyncCaptain client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", ) async def main() -> None: - response = await client.query.collection_v2stream( - collection_name="collection_name", - query="query", + await client.query.collection_v2( + collection_name="my_documents", + query="What are the key terms in the contract?", + inference=True, + stream=True, + rerank=True, + top_k=10, + include_bbox=False, ) - async for chunk in response.data: - yield chunk asyncio.run(main()) @@ -85,7 +92,7 @@ will be thrown. from runcaptain.core.api_error import ApiError try: - client.query.collection_v2stream(...) + client.query.collection_v2(...) except ApiError as e: print(e.status_code) print(e.body) @@ -120,15 +127,11 @@ The `.with_raw_response` property returns a "raw" client that can be used to acc ```python from runcaptain import Captain -client = Captain( - ..., -) -with client.query.with_raw_response.collection_v2stream(...) as response: - print( - response.headers - ) # access the response headersprint(response.status_code) # access the response status code - for chunk in response.data: - print(chunk) # access the underlying object(s) +client = Captain(...) +response = client.query.with_raw_response.collection_v2(...) +print(response.headers) # access the response headers +print(response.status_code) # access the response status code +print(response.data) # access the underlying object ``` ### Retries @@ -146,7 +149,7 @@ A request is deemed retryable when any of the following HTTP status codes is ret Use the `max_retries` request option to configure this behavior. ```python -client.query.collection_v2stream(..., request_options={ +client.query.collection_v2(..., request_options={ "max_retries": 1 }) ``` @@ -156,17 +159,12 @@ client.query.collection_v2stream(..., request_options={ The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. ```python - from runcaptain import Captain -client = Captain( - ..., - timeout=20.0, -) - +client = Captain(..., timeout=20.0) # Override timeout for a specific method -client.query.collection_v2stream(..., request_options={ +client.query.collection_v2(..., request_options={ "timeout_in_seconds": 1 }) ``` diff --git a/poetry.lock b/poetry.lock index 83fdc8b..7a8fde4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,30 +11,24 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "anyio" -version = "4.5.2" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +version = "4.13.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, - {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, + {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, + {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] -trio = ["trio (>=0.26.1)"] +trio = ["trio (>=0.32.0)"] [[package]] name = "certifi" @@ -161,13 +155,13 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] @@ -236,45 +230,46 @@ files = [ [[package]] name = "packaging" -version = "26.0" +version = "26.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, + {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"}, + {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"}, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "2.10.6" +version = "2.13.3" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, + {file = "pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927"}, + {file = "pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" -typing-extensions = ">=4.12.2" +pydantic-core = "2.46.3" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -282,115 +277,135 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.46.3" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pydantic_core-2.46.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da3786b8018e60349680720158cc19161cc3b4bdd815beb0a321cd5ce1ad5b1"}, + {file = "pydantic_core-2.46.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc0988cb29d21bf4a9d5cf2ef970b5c0e38d8d8e107a493278c05dc6c1dda69f"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f9067c3bfadd04c55484b89c0d267981b2f3512850f6f66e1e74204a4e4ce3"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a642ac886ecf6402d9882d10c405dcf4b902abeb2972cd5fb4a48c83cd59279a"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79f561438481f28681584b89e2effb22855e2179880314bcddbf5968e935e807"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57a973eae4665352a47cf1a99b4ee864620f2fe663a217d7a8da68a1f3a5bfda"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83d002b97072a53ea150d63e0a3adfae5670cef5aa8a6e490240e482d3b22e57"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b40ddd51e7c44b28cfaef746c9d3c506d658885e0a46f9eeef2ee815cbf8e045"}, + {file = "pydantic_core-2.46.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac5ec7fb9b87f04ee839af2d53bcadea57ded7d229719f56c0ed895bff987943"}, + {file = "pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3b11c812f61b3129c4905781a2601dfdfdea5fe1e6c1cfb696b55d14e9c054f"}, + {file = "pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1108da631e602e5b3c38d6d04fe5bb3bfa54349e6918e3ca6cf570b2e2b2f9d4"}, + {file = "pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:de885175515bcfa98ae618c1df7a072f13d179f81376c8007112af20567fd08a"}, + {file = "pydantic_core-2.46.3-cp310-cp310-win32.whl", hash = "sha256:d11058e3201527d41bc6b545c79187c9e4bf85e15a236a6007f0e991518882b7"}, + {file = "pydantic_core-2.46.3-cp310-cp310-win_amd64.whl", hash = "sha256:3612edf65c8ea67ac13616c4d23af12faef1ae435a8a93e5934c2a0cbbdd1fd6"}, + {file = "pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5"}, + {file = "pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa"}, + {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c"}, + {file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf"}, + {file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b"}, + {file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e"}, + {file = "pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb"}, + {file = "pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346"}, + {file = "pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6"}, + {file = "pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67"}, + {file = "pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396"}, + {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d"}, + {file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca"}, + {file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976"}, + {file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b"}, + {file = "pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4"}, + {file = "pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1"}, + {file = "pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72"}, + {file = "pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37"}, + {file = "pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687"}, + {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3"}, + {file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022"}, + {file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23"}, + {file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7"}, + {file = "pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13"}, + {file = "pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0"}, + {file = "pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec"}, + {file = "pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b"}, + {file = "pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22"}, + {file = "pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f"}, + {file = "pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127"}, + {file = "pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c"}, + {file = "pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1"}, + {file = "pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505"}, + {file = "pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e"}, + {file = "pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8"}, + {file = "pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374"}, + {file = "pydantic_core-2.46.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fa3eb7c2995aa443687a825bc30395c8521b7c6ec201966e55debfd1128bcceb"}, + {file = "pydantic_core-2.46.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d08782c4045f90724b44c95d35ebec0d67edb8a957a2ac81d5a8e4b8a200495"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:831eb19aa789a97356979e94c981e5667759301fb708d1c0d5adf1bc0098b873"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4335e87c7afa436a0dfa899e138d57a72f8aad542e2cf19c36fb428461caabd0"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99421e7684a60f7f3550a1d159ade5fdff1954baedb6bdd407cba6a307c9f27d"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd81f6907932ebac3abbe41378dac64b2380db1287e2aa64d8d88f78d170f51a"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f247596366f4221af52beddd65af1218797771d6989bc891a0b86ccaa019168"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:6dff8cc884679df229ebc6d8eb2321ea6f8e091bc7d4886d4dc2e0e71452843c"}, + {file = "pydantic_core-2.46.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68ef2f623dda6d5a9067ac014e406c020c780b2a358930a7e5c1b73702900720"}, + {file = "pydantic_core-2.46.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d56bdb4af1767cc15b0386b3c581fdfe659bb9ee4a4f776e92c1cd9d074000d6"}, + {file = "pydantic_core-2.46.3-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:91249bcb7c165c2fb2a2f852dbc5c91636e2e218e75d96dfdd517e4078e173dd"}, + {file = "pydantic_core-2.46.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b068543bdb707f5d935dab765d99227aa2545ef2820935f2e5dd801795c7dbd"}, + {file = "pydantic_core-2.46.3-cp39-cp39-win32.whl", hash = "sha256:dcda6583921c05a40533f982321532f2d8db29326c7b95c4026941fa5074bd79"}, + {file = "pydantic_core-2.46.3-cp39-cp39-win_amd64.whl", hash = "sha256:a35cc284c8dd7edae8a31533713b4d2467dfe7c4f1b5587dd4031f28f90d1d13"}, + {file = "pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:9715525891ed524a0a1eb6d053c74d4d4ad5017677fb00af0b7c2644a31bae46"}, + {file = "pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:9d2f400712a99a013aff420ef1eb9be077f8189a36c1e3ef87660b4e1088a874"}, + {file = "pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd2aab0e2e9dc2daf36bd2686c982535d5e7b1d930a1344a7bb6e82baab42a76"}, + {file = "pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e9d76736da5f362fabfeea6a69b13b7f2be405c6d6966f06b2f6bfff7e64531"}, + {file = "pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803"}, + {file = "pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3"}, + {file = "pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5"}, + {file = "pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8"}, + {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff"}, + {file = "pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pytest" @@ -434,13 +449,13 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-xdist" -version = "3.6.1" +version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, ] [package.dependencies] @@ -504,96 +519,99 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - [[package]] name = "tomli" -version = "2.4.0" +version = "2.4.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, - {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, - {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, - {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, - {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, - {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, - {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, - {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, - {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, - {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, - {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, - {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, - {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, - {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, - {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, - {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, - {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, - {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, - {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] [[package]] name = "types-python-dateutil" -version = "2.9.0.20241206" +version = "2.9.0.20260408" description = "Typing stubs for python-dateutil" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, - {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, + {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, + {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, ] [[package]] name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] +[package.dependencies] +typing-extensions = ">=4.12.0" + [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "bcf31a142c86d9e556553c8c260a93b563ac64a043076dbd48b26111d422c26e" +python-versions = "^3.10" +content-hash = "93a8c8edb6ca8fb7babafba6a771bd007d1c44858ecfe1cd4ff3e0985fbcfa42" diff --git a/pyproject.toml b/pyproject.toml index 23aa5a8..59810e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "captain-sdk" -version = "0.1.1" +version = "0.1.2" description = "" readme = "README.md" authors = [] @@ -18,8 +18,6 @@ classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -44,7 +42,7 @@ Homepage = 'https://www.runcaptain.com' Repository = 'https://github.com/runcaptain/captain-py-sdk' [tool.poetry.dependencies] -python = "^3.8" +python = "^3.10" httpx = ">=0.21.2" pydantic = ">= 1.9.2" pydantic-core = ">=2.18.2" diff --git a/reference.md b/reference.md index 923baf7..4e18406 100644 --- a/reference.md +++ b/reference.md @@ -1,6 +1,6 @@ # Reference ## Collections -
client.collections.list_collections_v2() -> AsyncHttpResponse[CollectionListResponseV2] +
client.collections.list_collections_v2(...) -> CollectionListResponseV2
@@ -30,11 +30,14 @@ Returns an array of collection objects with collection_name, collection_id, and ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.list_collections_v2() ``` @@ -51,6 +54,22 @@ client.collections.list_collections_v2()
+**limit:** `typing.Optional[int]` — Maximum number of collections to return + +
+
+ +
+
+ +**offset:** `typing.Optional[int]` — Pagination offset + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -63,7 +82,7 @@ client.collections.list_collections_v2()
-
client.collections.create_collection_v2(...) -> AsyncHttpResponse[CollectionResponseV2] +
client.collections.create_collection_v2(...) -> CollectionResponseV2
@@ -91,11 +110,14 @@ Create a new collection (idempotent). Returns 201 if created, 200 if already exi ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.create_collection_v2( collection_name="my_documents", description="A collection of research documents", @@ -115,7 +137,7 @@ client.collections.create_collection_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to create
@@ -143,7 +165,7 @@ client.collections.create_collection_v2(
-
client.collections.delete_collection_v2(...) -> AsyncHttpResponse[StandardResponseV2] +
client.collections.delete_collection_v2(...) -> StandardResponseV2
@@ -171,11 +193,14 @@ Delete a collection and all its indexed documents. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.delete_collection_v2( collection_name="my_documents", ) @@ -194,7 +219,7 @@ client.collections.delete_collection_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to delete
@@ -214,7 +239,7 @@ client.collections.delete_collection_v2(
-
client.collections.change_collection_environment_v2(...) -> AsyncHttpResponse[ChangeEnvironmentResponseV2] +
client.collections.change_collection_environment_v2(...) -> ChangeEnvironmentResponseV2
@@ -228,7 +253,7 @@ client.collections.delete_collection_v2( Move a collection from one environment to another (e.g., development to production) without reindexing. -All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same — only the environment label changes. +All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same - only the environment label changes. ## Use Cases - Promote a development collection to production after testing @@ -254,11 +279,14 @@ All files, indexed data, and vector embeddings are preserved. The collection's i ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.change_collection_environment_v2( collection_name="my_documents", new_environment="production", @@ -278,7 +306,7 @@ client.collections.change_collection_environment_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to move
@@ -306,7 +334,7 @@ client.collections.change_collection_environment_v2(
-
client.collections.list_documents_v2(...) -> AsyncHttpResponse[DocumentListResponseV2] +
client.collections.list_documents_v2(...) -> DocumentListResponseV2
@@ -334,11 +362,14 @@ List all documents in a collection with pagination support. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.list_documents_v2( collection_name="my_documents", ) @@ -357,7 +388,15 @@ client.collections.list_documents_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of documents to return
@@ -385,7 +424,7 @@ client.collections.list_documents_v2(
-
client.collections.wipe_collection_documents_v2(...) -> AsyncHttpResponse[DocumentDeleteResponseV2] +
client.collections.wipe_collection_documents_v2(...) -> DocumentDeleteResponseV2
@@ -413,13 +452,16 @@ Remove all documents from a collection while keeping the collection structure. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.wipe_collection_documents_v2( - collection_name="my_documents", + collection_name="customer_profiles", ) ``` @@ -436,7 +478,7 @@ client.collections.wipe_collection_documents_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to wipe
@@ -456,7 +498,7 @@ client.collections.wipe_collection_documents_v2(
-
client.collections.delete_document_v2(...) -> AsyncHttpResponse[StandardResponseV2] +
client.collections.delete_document_v2(...) -> StandardResponseV2
@@ -484,14 +526,17 @@ Delete a specific document from a collection. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.collections.delete_document_v2( - collection_name="my_documents", - document_id="doc_abc123", + collection_name="customer_feedback", + document_id="a1b2c3d4e5f678901234567890abcdef", ) ``` @@ -508,7 +553,7 @@ client.collections.delete_document_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection
@@ -516,7 +561,7 @@ client.collections.delete_document_v2(
-**document_id:** `str` +**document_id:** `str` — ID of the document to delete
@@ -537,7 +582,7 @@ client.collections.delete_document_v2(
## Query -
client.query.collection_v2stream(...) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[QueryStreamEvent]]] +
client.query.collection_v2(...) -> QueryResponseV2
@@ -586,8 +631,22 @@ data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1} ### Notes - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. -- Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. +- Text chunks are interleaved between tool events - text arrives after the agent has gathered results from a search. - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. + +## Bounding Box Data + +Set `include_bbox: true` (inference=false only) to receive element-level layout coordinates for each search result. Each result will include a `layout` object with normalized bounding box blocks for PDF and DOCX files. + +Each block contains: +- `type`: element type (text, title, section_header, list_item, table, figure, key_value, header, footer) +- `content`: the text content +- `page`: page number +- `bbox`: normalized 0-1 coordinates `{ top, left, width, height }` relative to page dimensions +- `confidence`: extraction confidence (high/low) when available +- `image_url`: presigned URL for figure/chart images when available + +Files without OCR data (TXT, CSV, images) will have `layout: null`.
@@ -603,17 +662,23 @@ data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1} ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -response = client.query.collection_v2stream( - collection_name="collection_name", - query="query", + +client.query.collection_v2( + collection_name="my_documents", + query="What are the key terms in the contract?", + inference=True, + stream=True, + rerank=True, + top_k=10, + include_bbox=False, ) -for chunk in response.data: - yield chunk ``` @@ -629,7 +694,7 @@ for chunk in response.data:
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to query
@@ -645,39 +710,7 @@ for chunk in response.data:
-**inference:** `typing.Optional[bool]` — Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. - -
-
- -
-
- -**top_k:** `typing.Optional[int]` — Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). - -
-
- -
-
- -**rerank:** `typing.Optional[bool]` — Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. - -
-
- -
-
- -**metadata_filter:** `typing.Optional[typing.Dict[str, typing.Any]]` — Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or - -
-
- -
-
- -**custom_prompt:** `typing.Optional[str]` — Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. +**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication
@@ -685,112 +718,15 @@ for chunk in response.data:
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. +**inference:** `typing.Optional[bool]` — Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results.
- - - - - - -
- -
client.query.collection_v2(...) -> AsyncHttpResponse[QueryResponseV2] -
-
- -#### 📝 Description - -
-
- -
-
- -Execute a natural language query against a collection. - -When `inference=true`, returns an AI-generated response with relevant documents. -When `inference=false`, returns raw search results with content and metadata. - -## Streaming (SSE) - -When `stream: true` and `inference: true`, the response is a Server-Sent Events stream. Every `data:` field is a JSON object with a `type` discriminator. - -### SSE Event Types - -| `type` value | Schema | Description | -|---|---|---| -| `text.delta` | `QueryStreamTextEvent` | Incremental text chunk of the AI response. | -| `tool.start` | `QueryStreamToolStartEvent` | The agent is performing a knowledge-base search. | -| `tool.end` | `QueryStreamToolEndEvent` | A tool call completed. `tool_call_id` correlates with the preceding `tool.start`. | -| `stream_complete` | `QueryStreamCompleteEvent` | Stream finished successfully. Close the connection. | -| `stream_error` | `QueryStreamErrorEvent` | An error occurred. Close the connection. | - -### Example SSE Stream - -``` -data: {"type":"tool.start","seq":1,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","args":{"query":"revenue projections Q4"}} - -data: {"type":"tool.end","seq":2,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","ok":true,"result_summary":{"resultCount":12}} - -data: {"type":"text.delta","seq":3,"run_id":"run_abc","data":"Based on the documents"} -data: {"type":"text.delta","seq":4,"run_id":"run_abc","data":" provided, the revenue"} -data: {"type":"text.delta","seq":5,"run_id":"run_abc","data":" projections for Q4 show"} -data: {"type":"text.delta","seq":6,"run_id":"run_abc","data":" a 15% increase over Q3."} - -data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1},"stats":{"totalTokens":150}} -``` - -### Notes - -- The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. -- Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. -- Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from runcaptain import Captain - -client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.query.collection_v2( - collection_name="my_documents", - query="What are the key terms in the contract?", - inference=True, - rerank=True, - top_k=10, -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
-**collection_name:** `str` +**stream:** `typing.Optional[bool]` — Enable real-time streaming of the response
@@ -798,7 +734,7 @@ client.query.collection_v2(
-**query:** `str` — The natural language query to search for +**top_k:** `typing.Optional[int]` — Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy).
@@ -806,7 +742,7 @@ client.query.collection_v2(
-**inference:** `typing.Optional[bool]` — Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. +**rerank:** `typing.Optional[bool]` — Enable reranking for improved relevance ordering. Uses Gemini Flash 2.5 by default, or Voyage AI rerank-2.5 as fallback. Adds ~100-300ms latency.
@@ -814,7 +750,7 @@ client.query.collection_v2(
-**top_k:** `typing.Optional[int]` — Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). +**metadata_filter:** `typing.Optional[typing.Dict[str, typing.Any]]` — Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or
@@ -822,7 +758,7 @@ client.query.collection_v2(
-**rerank:** `typing.Optional[bool]` — Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. +**custom_prompt:** `typing.Optional[str]` — Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context.
@@ -830,7 +766,7 @@ client.query.collection_v2(
-**metadata_filter:** `typing.Optional[typing.Dict[str, typing.Any]]` — Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or +**include_bbox:** `typing.Optional[bool]` — Include normalized bounding box layout data for each search result. Returns element-level positions (titles, paragraphs, tables, figures, form fields) with page coordinates for PDF and DOCX files. Only supported with inference=false.
@@ -838,7 +774,7 @@ client.query.collection_v2(
-**custom_prompt:** `typing.Optional[str]` — Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. +**search_results:** `typing.Optional[bool]` — When inference=true, include the raw search result chunks that were used as context for the LLM response. Defaults to false. Always true when inference=false.
@@ -859,7 +795,7 @@ client.query.collection_v2(
## Indexing -
client.indexing.index_s3bucket_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_s3bucket_v2(...) -> IndexJobResponseV2
@@ -887,11 +823,14 @@ Index all files from an S3 bucket into a collection. Returns a job_id for tracki ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_s3bucket_v2( collection_name="my_documents", bucket_name="my-documents-bucket", @@ -916,7 +855,7 @@ client.indexing.index_s3bucket_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -956,6 +895,14 @@ client.indexing.index_s3bucket_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **bucket_region:** `typing.Optional[str]` — AWS region where the bucket is located
@@ -988,6 +935,14 @@ client.indexing.index_s3bucket_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1000,7 +955,7 @@ client.indexing.index_s3bucket_v2(
-
client.indexing.index_s3file_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_s3file_v2(...) -> IndexJobResponseV2
@@ -1028,11 +983,14 @@ Index a single file from an S3 bucket into a collection. Returns a job_id for tr ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_s3file_v2( collection_name="my_documents", bucket_name="my-documents-bucket", @@ -1057,7 +1015,7 @@ client.indexing.index_s3file_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1121,6 +1079,14 @@ client.indexing.index_s3file_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1133,7 +1099,7 @@ client.indexing.index_s3file_v2(
-
client.indexing.index_gcs_bucket_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_gcs_bucket_v2(...) -> IndexJobResponseV2
@@ -1161,15 +1127,18 @@ Index all files from a Google Cloud Storage bucket into a collection. Returns a ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_gcs_bucket_v2( collection_name="my_documents", bucket_name="my-gcs-documents", - service_account_json='{"type": "service_account", "project_id": "my-project", ...}', + service_account_json="{\"type\": \"service_account\", \"project_id\": \"my-project\", ...}", processing_type="advanced", ) @@ -1187,7 +1156,7 @@ client.indexing.index_gcs_bucket_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1243,6 +1212,14 @@ client.indexing.index_gcs_bucket_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1255,7 +1232,7 @@ client.indexing.index_gcs_bucket_v2(
-
client.indexing.index_gcs_file_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_gcs_file_v2(...) -> IndexJobResponseV2
@@ -1283,16 +1260,19 @@ Index a single file from a GCS bucket into a collection. Returns a job_id for tr ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_gcs_file_v2( collection_name="my_documents", bucket_name="my-gcs-documents", file_uri="gs://my-gcs-documents/reports/annual-review.pdf", - service_account_json='{"type": "service_account", "project_id": "my-project", ...}', + service_account_json="{\"type\": \"service_account\", \"project_id\": \"my-project\", ...}", processing_type="advanced", ) @@ -1310,7 +1290,7 @@ client.indexing.index_gcs_file_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1358,6 +1338,14 @@ client.indexing.index_gcs_file_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1370,7 +1358,7 @@ client.indexing.index_gcs_file_v2(
-
client.indexing.index_s3directory_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_s3directory_v2(...) -> IndexJobResponseV2
@@ -1398,11 +1386,14 @@ Index all files from a specific directory in an S3 bucket into a collection. Use ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_s3directory_v2( collection_name="my_documents", bucket_name="my-documents-bucket", @@ -1427,7 +1418,7 @@ client.indexing.index_s3directory_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1475,6 +1466,14 @@ client.indexing.index_s3directory_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **bucket_region:** `typing.Optional[str]` — AWS region where the bucket is located
@@ -1507,6 +1506,14 @@ client.indexing.index_s3directory_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1519,7 +1526,7 @@ client.indexing.index_s3directory_v2(
-
client.indexing.index_gcs_directory_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_gcs_directory_v2(...) -> IndexJobResponseV2
@@ -1547,16 +1554,19 @@ Index all files from a specific directory in a GCS bucket into a collection. Use ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_gcs_directory_v2( collection_name="my_documents", bucket_name="my-gcs-documents", directory_path="reports/2025/", - service_account_json='{"type": "service_account", "project_id": "my-project", ...}', + service_account_json="{\"type\": \"service_account\", \"project_id\": \"my-project\", ...}", processing_type="advanced", ) @@ -1574,7 +1584,7 @@ client.indexing.index_gcs_directory_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1614,6 +1624,14 @@ client.indexing.index_gcs_directory_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **max_files:** `typing.Optional[int]` — Maximum number of files to index (optional)
@@ -1638,6 +1656,14 @@ client.indexing.index_gcs_directory_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1650,7 +1676,7 @@ client.indexing.index_gcs_directory_v2(
-
client.indexing.index_azure_container_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_azure_container_v2(...) -> IndexJobResponseV2
@@ -1678,11 +1704,14 @@ Index all files from an Azure Blob Storage container into a collection. Returns ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_azure_container_v2( collection_name="my_documents", container_name="my-azure-documents", @@ -1705,7 +1734,7 @@ client.indexing.index_azure_container_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1745,6 +1774,14 @@ client.indexing.index_azure_container_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **max_files:** `typing.Optional[int]` — Maximum number of files to index (optional)
@@ -1769,11 +1806,19 @@ client.indexing.index_azure_container_v2(
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. +**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio.
-
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
@@ -1781,7 +1826,7 @@ client.indexing.index_azure_container_v2(
-
client.indexing.index_azure_file_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_azure_file_v2(...) -> IndexJobResponseV2
@@ -1809,11 +1854,14 @@ Index a single file from an Azure Blob Storage container into a collection. Retu ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_azure_file_v2( collection_name="my_documents", container_name="my-azure-documents", @@ -1837,7 +1885,7 @@ client.indexing.index_azure_file_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -1893,6 +1941,14 @@ client.indexing.index_azure_file_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1905,7 +1961,7 @@ client.indexing.index_azure_file_v2(
-
client.indexing.index_azure_directory_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_azure_directory_v2(...) -> IndexJobResponseV2
@@ -1933,11 +1989,14 @@ Index all files from a specific directory (prefix) in an Azure Blob Storage cont ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_azure_directory_v2( collection_name="my_documents", container_name="my-azure-documents", @@ -1961,7 +2020,7 @@ client.indexing.index_azure_directory_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -2009,6 +2068,14 @@ client.indexing.index_azure_directory_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **max_files:** `typing.Optional[int]` — Maximum number of files to index (optional)
@@ -2033,6 +2100,14 @@ client.indexing.index_azure_directory_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2045,7 +2120,7 @@ client.indexing.index_azure_directory_v2(
-
client.indexing.index_r2bucket_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_r2bucket_v2(...) -> IndexJobResponseV2
@@ -2057,7 +2132,7 @@ client.indexing.index_azure_directory_v2(
-Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible — provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. +Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible - provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}.
@@ -2073,11 +2148,14 @@ Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatib ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_r2bucket_v2( collection_name="my_documents", bucket_name="my-r2-bucket", @@ -2101,7 +2179,7 @@ client.indexing.index_r2bucket_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -2149,6 +2227,14 @@ client.indexing.index_r2bucket_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **jurisdiction:** `typing.Optional[IndexR2RequestV2Jurisdiction]` — R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage.
@@ -2181,6 +2267,14 @@ client.indexing.index_r2bucket_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2193,7 +2287,7 @@ client.indexing.index_r2bucket_v2(
-
client.indexing.index_r2file_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_r2file_v2(...) -> IndexJobResponseV2
@@ -2221,11 +2315,14 @@ Index a single file from a Cloudflare R2 bucket into a collection. Returns a job ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_r2file_v2( collection_name="my_documents", bucket_name="my-r2-bucket", @@ -2250,7 +2347,7 @@ client.indexing.index_r2file_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -2322,6 +2419,14 @@ client.indexing.index_r2file_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2334,7 +2439,7 @@ client.indexing.index_r2file_v2(
-
client.indexing.index_r2directory_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_r2directory_v2(...) -> IndexJobResponseV2
@@ -2362,11 +2467,14 @@ Index all files from a specific directory (prefix) in a Cloudflare R2 bucket int ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_r2directory_v2( collection_name="my_documents", bucket_name="my-r2-bucket", @@ -2391,7 +2499,7 @@ client.indexing.index_r2directory_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -2447,6 +2555,14 @@ client.indexing.index_r2directory_v2(
+**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ **jurisdiction:** `typing.Optional[IndexR2DirectoryRequestV2Jurisdiction]` — R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage.
@@ -2479,6 +2595,14 @@ client.indexing.index_r2directory_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2491,7 +2615,7 @@ client.indexing.index_r2directory_v2(
-
client.indexing.index_url_v2(...) -> AsyncHttpResponse[IndexJobResponseV2] +
client.indexing.index_url_v2(...) -> IndexJobResponseV2
@@ -2503,13 +2627,29 @@ client.indexing.index_r2directory_v2(
-Index documents from public URLs into a collection. No cloud storage credentials required. +Index documents or web pages from public URLs into a collection. No cloud storage credentials required. You can provide either: -- `url` — a single URL string for one document -- `urls` — an array of URL strings for multiple documents +- `url` - a single URL string +- `urls` - an array of URL strings + +## Smart Content Detection + +The endpoint automatically detects whether a URL points to a hosted file or a web page: -Supported file types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Documents are downloaded and processed through the same pipeline as cloud storage indexing. +- **Hosted files** (PDF, DOCX, XLSX, CSV, TXT, images, etc.) are downloaded and processed directly through the indexing pipeline. +- **Web pages** (HTML) are automatically scraped - text content is extracted as markdown and page images are downloaded and indexed. Bot-protected pages are handled via web unlocker technology. + +## Supported Content + +- **Documents**: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML +- **Images**: PNG, JPG, JPEG, GIF, BMP, TIFF +- **Web pages**: Any public URL serving HTML - text and images are extracted automatically + +## Processing Modes for Web Pages + +- **advanced**: Extracts text content as markdown AND downloads and indexes all page images +- **basic**: Extracts text content only - faster and lower cost Returns a job_id for tracking progress via GET /v2/jobs/{job_id}.
@@ -2527,18 +2667,17 @@ Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) + client.indexing.index_url_v2( collection_name="my_documents", - urls=[ - "https://example.com/documents/report.pdf", - "https://example.com/documents/memo.txt", - "https://example.com/documents/data.csv", - ], + url="https://example.com/documents/report.pdf", processing_type="advanced", ) @@ -2556,7 +2695,15 @@ client.indexing.index_url_v2(
-**collection_name:** `str` +**collection_name:** `str` — Name of the collection to index into + +
+
+ +
+
+ +**processing_type:** `IndexUrlRequestV2ProcessingType` — Processing mode. For hosted documents: 'advanced' enables AI-enhanced extraction for complex layouts, tables, figures, and charts; 'basic' provides standard document processing. For web pages: 'advanced' extracts both text content and page images; 'basic' extracts text content only (faster, lower cost).
@@ -2564,7 +2711,7 @@ client.indexing.index_url_v2(
-**processing_type:** `IndexUrlRequestV2ProcessingType` — Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. +**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication
@@ -2572,7 +2719,7 @@ client.indexing.index_url_v2(
-**url:** `typing.Optional[str]` — A single public URL to a hosted document. Supported types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Provide either 'url' or 'urls', not both. +**url:** `typing.Optional[str]` — A single public URL to a document or web page. Hosted files (PDF, DOCX, etc.) are indexed directly. Web pages (HTML) are automatically scraped - text and images are extracted. Provide either 'url' or 'urls', not both.
@@ -2580,7 +2727,7 @@ client.indexing.index_url_v2(
-**urls:** `typing.Optional[typing.Sequence[str]]` — An array of public URLs to hosted documents. Provide either 'url' or 'urls', not both. +**urls:** `typing.Optional[typing.List[str]]` — An array of public URLs to documents or web pages. Each URL is auto-detected - hosted files are indexed directly, web pages are scraped. Provide either 'url' or 'urls', not both.
@@ -2596,6 +2743,14 @@ client.indexing.index_url_v2(
+**parsing_script:** `typing.Optional[str]` — Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2608,8 +2763,7 @@ client.indexing.index_url_v2(
-## Jobs -
client.jobs.get_job_status_v2(...) -> AsyncHttpResponse[JobStatusResponseV2] +
client.indexing.index_youtube_v2(...) -> IndexJobResponseV2
@@ -2621,32 +2775,29 @@ client.indexing.index_url_v2(
-Get the status of an indexing job with detailed progress information. +Index YouTube video transcripts into a collection. -## Status Values -- **pending**: Job created but processing hasn't started yet -- **running**: Job is actively processing files -- **completed**: Job finished successfully -- **failed**: Job encountered an error -- **cancelled**: Job was cancelled by user +Fetches transcripts from YouTube videos using auto-generated or manual captions, formats them with inline timestamps, and indexes the text for semantic search. -## Processing Stages -When status is `running`, the `progress.current_stage` field indicates which stage: -1. **scanning**: Scanning bucket for files -2. **extracting**: Extracting text content from documents -3. **chunking**: Splitting documents into semantic chunks -4. **tagging**: AI tagging and summarization -5. **embedding**: Generating vector embeddings -6. **finalizing**: Aggregating results and recording billing +You can provide either: +- `url` - a single YouTube video URL +- `urls` - an array of YouTube video URLs (max 20) -## File Status Values -Each file in the `files` array has a status: -- **queued**: Waiting to be processed -- **processing**: Currently being processed -- **completed**: Successfully indexed -- **failed**: Failed to process (see error_code/error_message) -- **skipped**: Skipped (already indexed, unsupported type, etc.) -- **cancelled**: Processing was cancelled +Transcripts are always processed as basic text (no OCR needed). Each transcript is formatted with `[HH:MM:SS]` timestamp markers so search results can reference specific moments in the video. + +## Supported URL Formats +- `youtube.com/watch?v=VIDEO_ID` +- `youtu.be/VIDEO_ID` +- `youtube.com/shorts/VIDEO_ID` + +## Auto-Injected Metadata +The following metadata is automatically added to indexed chunks: +- `youtube_video_id` - the video ID +- `youtube_url` - the original video URL +- `youtube_language` - transcript language +- `youtube_duration_seconds` - total video duration + +Returns a job_id for tracking progress via GET /v2/jobs/{job_id}.
@@ -2662,13 +2813,17 @@ Each file in the `files` array has a status: ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.jobs.get_job_status_v2( - job_id="job_s3_abc123", + +client.indexing.index_youtube_v2( + collection_name="my_collection", + url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", ) ``` @@ -2685,7 +2840,7 @@ client.jobs.get_job_status_v2(
-**job_id:** `str` +**collection_name:** `str` — Name of the collection to index into
@@ -2693,74 +2848,39 @@ client.jobs.get_job_status_v2(
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. +**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication
- -
- - - - -
-
client.jobs.cancel_job_v2(...) -> AsyncHttpResponse[JobCancelResponseV2]
-#### 📝 Description - -
-
+**url:** `typing.Optional[str]` — A single YouTube video URL. Supported formats: youtube.com/watch?v=, youtu.be/, youtube.com/shorts/. Provide either 'url' or 'urls', not both. + +
+
-Cancel an indexing job. - -Behavior: -- If job is pending or running -> transitions to cancelled -- If job is already completed/failed/cancelled -> returns 200 with current state (idempotent) -
-
+**urls:** `typing.Optional[typing.List[str]]` — An array of YouTube video URLs to index (max 20). Provide either 'url' or 'urls', not both. +
-#### 🔌 Usage -
-
-
- -```python -from runcaptain import Captain - -client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.jobs.cancel_job_v2( - job_id="job_s3_abc123", -) - -``` -
-
+**languages:** `typing.Optional[typing.List[str]]` — Preferred transcript languages in priority order (ISO 639-1 codes). Defaults to English. Only specify if you need a non-English transcript (e.g., ['fr', 'de']). Falls back to auto-generated captions if manual transcript unavailable. +
-#### ⚙️ Parameters - -
-
-
-**job_id:** `str` +**custom_metadata:** `typing.Optional[typing.Dict[str, typing.Any]]` — Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings.
@@ -2780,8 +2900,7 @@ client.jobs.cancel_job_v2(
-## Datasets -
client.datasets.search_dataset(...) -> AsyncHttpResponse[DatasetSearchResponse] +
client.indexing.index_text_v2(...) -> IndexJobResponseV2
@@ -2793,12 +2912,13 @@ client.jobs.cancel_job_v2(
-Search for articles within a news dataset. +Index plain text content into a collection. -Contact your Account Executive for available datasets. +Accepts raw text content in the request body, saves it as a document, and indexes it for semantic search. No file upload or cloud storage needed. -## Response -Returns a list of search results with title, URL, snippet, and date. +Text is always processed as basic (no OCR). Ideal for indexing scraped content, notes, articles, or any plain text data. + +Returns a job_id for tracking progress via GET /v2/jobs/{job_id}.
@@ -2814,14 +2934,18 @@ Returns a list of search results with title, URL, snippet, and date. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.datasets.search_dataset( - dataset="dataset_name", - q="climate change policy", + +client.indexing.index_text_v2( + collection_name="my_collection", + content="Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience...", + filename="ml_notes.txt", ) ``` @@ -2838,7 +2962,7 @@ client.datasets.search_dataset(
-**dataset:** `typing.Optional[str]` — The dataset to search. Contact your Account Executive for available datasets. +**collection_name:** `str` — Name of the collection to index into
@@ -2846,7 +2970,7 @@ client.datasets.search_dataset(
-**q:** `str` — Search query +**content:** `str` — The text content to index.
@@ -2854,7 +2978,23 @@ client.datasets.search_dataset(
-**limit:** `typing.Optional[int]` — Maximum number of results to return (default: 10, max: 100) +**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ +**filename:** `typing.Optional[str]` — Optional filename for the text document. Defaults to 'snippet-{N}.txt' where N auto-increments. + +
+
+ +
+
+ +**custom_metadata:** `typing.Optional[typing.Dict[str, typing.Any]]` — Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings.
@@ -2874,7 +3014,7 @@ client.datasets.search_dataset(
-
client.datasets.batch_search_datasets(...) -> AsyncHttpResponse[BatchSearchDatasetsResponse] +
client.indexing.index_file_v2(...) -> IndexJobResponseV2
@@ -2886,23 +3026,22 @@ client.datasets.search_dataset(
-Search for articles across multiple news datasets in a single request. +Upload and index files directly into a collection via multipart form-data. -Searches the same query across all specified datasets simultaneously. If no datasets are specified, searches all available datasets. +Upload one or more files (max 20) in a single request. Supports PDF, DOCX, XLSX, CSV, TXT, images, and other document types. Files are processed through the same pipeline as cloud storage indexing. -## Supported Datasets -- `nytimes` - New York Times -- `washpost` - Washington Post -- `sfstandard` - SF Standard -- `sacbee` - Sacramento Bee -- `sfchronicle` - San Francisco Chronicle -- `newyorker` - The New Yorker -- `theatlantic` - The Atlantic -- `sjmercury` - San Jose Mercury News -- `latimes` - Los Angeles Times +## Supported File Types +PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF -## Response -Returns results grouped by dataset source, with title, URL, snippet, and date for each article. +## Size Limits +- Maximum 100MB per file +- Maximum 20 files per request + +## Processing Modes +- **advanced**: AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images (2.5 credits/page) +- **basic**: Standard document processing optimized for general indexing (1 credit/page) + +Returns a job_id for tracking progress via GET /v2/jobs/{job_id}.
@@ -2918,15 +3057,18 @@ Returns results grouped by dataset source, with title, URL, snippet, and date fo ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.datasets.batch_search_datasets( - q="artificial intelligence regulation", - datasets=["nytimes", "washpost", "theatlantic"], - limit=10, + +client.indexing.index_file_v2( + collection_name="my_collection", + files=["example_files"], + processing_type="advanced", ) ``` @@ -2943,7 +3085,7 @@ client.datasets.batch_search_datasets(
-**q:** `str` — Search query +**collection_name:** `str` — Name of the collection to index into
@@ -2951,7 +3093,7 @@ client.datasets.batch_search_datasets(
-**datasets:** `typing.Optional[typing.Sequence[str]]` — List of dataset names to search. Defaults to all datasets if not provided. +**files:** `typing.List[core.File]` — One or more files to upload and index (max 20)
@@ -2959,7 +3101,23 @@ client.datasets.batch_search_datasets(
-**limit:** `typing.Optional[int]` — Maximum number of results to return (default: 10, max: 100) +**idempotency_key:** `typing.Optional[str]` — UUID for request deduplication + +
+
+ +
+
+ +**processing_type:** `typing.Optional[IndexFileV2RequestProcessingType]` — Document processing type: 'advanced' for AI-enhanced extraction, 'basic' for standard processing + +
+
+ +
+
+ +**custom_metadata:** `typing.Optional[str]` — JSON string of custom metadata to attach to all indexed chunks
@@ -2979,7 +3137,7 @@ client.datasets.batch_search_datasets(
-
client.datasets.get_dataset_article(...) -> AsyncHttpResponse[DatasetArticleResponse] +
client.indexing.validate_parsing_script_v2(...) -> ValidateParsingScriptResponseV2
@@ -2991,15 +3149,7 @@ client.datasets.batch_search_datasets(
-Get a full article from a supported news dataset. - -Contact your Account Executive for available datasets. - -## URL Path -The article URL is appended directly to the endpoint path. The URL must match the domain of the specified dataset. - -## Response -Returns the full article content in markdown format, along with metadata like title, author, date, and character count. +Validates a JavaScript parsing script without running it against real data. Upload your .js file as multipart/form-data under the file field. Checks that the script parses cleanly and exports a default function. Use this before uploading a script to catch syntax errors and structural problems. The return-type contract (must return a string) is enforced at indexing time by json_handler against your real JSON.
@@ -3015,14 +3165,16 @@ Returns the full article content in markdown format, along with metadata like ti ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.datasets.get_dataset_article( - dataset="dataset_name", - url="https://www.example.com/2025/01/15/politics/example-article", + +client.indexing.validate_parsing_script_v2( + file="example_file", ) ``` @@ -3039,15 +3191,7 @@ client.datasets.get_dataset_article(
-**dataset:** `typing.Optional[str]` — The dataset to get articles from. Contact your Account Executive for available datasets. - -
-
- -
-
- -**url:** `str` +**file:** `core.File` — The .js parsing script file to validate. Max 1 MB.
@@ -3067,8 +3211,8 @@ client.datasets.get_dataset_article(
-## General -
client.general.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## Jobs +
client.jobs.get_job_status_v2(...) -> JobStatusResponseV2
@@ -3080,15 +3224,1036 @@ client.datasets.get_dataset_article(
-Search across all entity types (companies, people, investors, funds, deals, etc.) in a unified query. Returns matching entities with their type and basic information. Use this for broad discovery before drilling into specific entity types. -
-
-
-
- -#### 🔌 Usage +Get the status of an indexing job with detailed progress information. -
+## Status Values +- **pending**: Job created but processing hasn't started yet +- **running**: Job is actively processing files +- **completed**: Job finished successfully +- **failed**: Job encountered an error +- **cancelled**: Job was cancelled by user + +## Processing Stages +When status is `running`, the `progress.current_stage` field indicates which stage: +1. **scanning**: Scanning bucket for files +2. **extracting**: Extracting text content from documents +3. **chunking**: Splitting documents into semantic chunks +4. **tagging**: AI tagging and summarization +5. **embedding**: Generating vector embeddings +6. **finalizing**: Aggregating results and recording billing + +## File Status Values +Each file in the `files` array has a status: +- **queued**: Waiting to be processed +- **processing**: Currently being processed +- **completed**: Successfully indexed +- **failed**: Failed to process (see error_code/error_message) +- **skipped**: Skipped (already indexed, unsupported type, etc.) +- **cancelled**: Processing was cancelled + +
+ + + +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.jobs.get_job_status_v2( + job_id="job_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**job_id:** `str` — The job ID returned from an indexing request + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + + + +
+ +
client.jobs.delete_job_v2(...) -> JobCancelResponseV2 +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cancel and delete an indexing job. + +Behavior: +- If job is pending or running -> transitions to cancelled +- If job is already completed/failed/cancelled -> returns 200 with current state (idempotent) +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.jobs.delete_job_v2( + job_id="job_s3_abc123", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**job_id:** `str` — The job ID to delete/cancel + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.jobs.rollback_job_v2(...) -> JobRollbackResponseV2 +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Rollback a completed or failed indexing job - removes all indexed files and their associated data. + +## Behavior +- **Running job**: Returns `409 Conflict` - cancel the job first using `DELETE /v2/jobs/{job_id}` +- **Completed/Failed/Cancelled job**: Deletes all files indexed by this job and returns the list of files removed +- **Not found**: Returns `404` + +## Use Cases +- Undo a completed indexing job that indexed incorrect data +- Clean up partial data from a failed job +- Remove test data after development/staging indexing runs +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.jobs.rollback_job_v2( + job_id="abc123xyz-1234567890", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**job_id:** `str` — The job ID to rollback + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Datasets +
client.datasets.search_dataset(...) -> DatasetSearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Search for articles within a news dataset. + +Contact your Account Executive for available datasets. + +## Response +Returns a list of search results with title, URL, snippet, and date. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.datasets.search_dataset( + dataset="nytimes", + q="latest technology trends", + limit=10, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — Search query + +
+
+ +
+
+ +**dataset:** `typing.Optional[str]` — The dataset to search. Contact your Account Executive for available datasets. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (default: 10, max: 100) + +
+
+ +
+
+ +**author:** `typing.Optional[str]` — Filter results by author/byline name. Used as an AND condition with `q` — returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.datasets.batch_search_datasets(...) -> BatchSearchDatasetsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Search for articles across multiple news datasets in a single request. + +Searches the same query across all specified datasets simultaneously. If no datasets are specified, searches all available datasets. + +Contact your Account Executive for available datasets. + +## Response +Returns results grouped by dataset source, with title, URL, snippet, author, and date for each article. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.datasets.batch_search_datasets( + q="artificial intelligence regulation", + limit=10, + datasets=[ + "nytimes", + "washpost", + "theatlantic" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — Search query + +
+
+ +
+
+ +**datasets:** `typing.Optional[typing.List[str]]` — List of dataset names to search. Defaults to all datasets if not provided. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (default: 10, max: 100) + +
+
+ +
+
+ +**author:** `typing.Optional[str]` — Filter results by author/byline name. Used as an AND condition with `q` - returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.datasets.get_dataset_article(...) -> DatasetArticleResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get a full article from a supported news dataset. + +Contact your Account Executive for available datasets. + +## URL Path +The article URL is appended directly to the endpoint path. The URL must match the domain of the specified dataset. + +## Response +Returns the full article content in markdown format, along with metadata like title, author, date, and character count. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.datasets.get_dataset_article( + dataset="dataset_name", + url="https://www.example.com/2025/01/15/politics/example-article", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**url:** `str` + +
+
+ +
+
+ +**dataset:** `typing.Optional[str]` — The dataset to get articles from. Contact your Account Executive for available datasets. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.datasets.search_medical_papers(...) -> ScientificAskResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Search medical and biomedical papers with a natural-language question. +Federates PubMed, PMC full-text, ClinicalTrials.gov, and Semantic Scholar, +then synthesizes a cited answer. + +`stream=true` returns text/event-stream with `tool_use`, `tool_result_summary`, +`text_delta`, and `done` event types. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.datasets.search_medical_papers( + question="What is the evidence that BRCA1-mutated breast cancer patients benefit from PARP inhibitors?", + max_sources=10, + include_trials=True, + recency_years=10, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**question:** `str` — Natural-language question. + +
+
+ +
+
+ +**max_sources:** `typing.Optional[int]` — Target number of cited sources in the final answer. + +
+
+ +
+
+ +**include_trials:** `typing.Optional[bool]` — Whether the agent may call ClinicalTrials.gov. + +
+
+ +
+
+ +**recency_years:** `typing.Optional[int]` — Prefer evidence within the last N years where the question allows. + +
+
+ +
+
+ +**stream:** `typing.Optional[bool]` — If true, response is text/event-stream; otherwise JSON. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## General +
client.general.search(...) -> GeneralSearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cross-entity search across companies and people. + +Returns the company match first, then executives (C-suite) at that company, then other employees. + +Supports `limit` parameter to control results count (default: 25, max: 100). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.general.search( + q="AI safety researchers at OpenAI", + limit=10, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — Search query across all entity types (companies, people, investors) + +
+
+ +
+
+ +**entity_type:** `typing.Optional[GeneralSearchRequestEntityType]` — Filter by entity type + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.general.search_shared(...) -> GeneralSearchSharedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Shared/saved search across entities. + +Same as cross-entity search with shared filter support. Returns companies and people matching the query. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.general.search_shared() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Search query (optional) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.general.entity_people(...) -> GeneralEntityPeopleResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get people associated with an entity (company employees/leadership). + +Returns executives first (C-suite, VP, Director), then other employees. Results are deduplicated. + +Supports `limit` parameter (default: 25, max: 100). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.general.entity_people( + entity_id="openai", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**entity_id:** `str` — Entity ID (any type) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results to return (default: 25, max: 100) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.general.entity_locations(...) -> GeneralEntityLocationsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get office locations for any entity. Returns headquarters and branch office addresses with city, state, country, and full address details. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.general.entity_locations( + entity_id="openai", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**entity_id:** `str` — Entity ID (any type) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.general.entity_affiliates(...) -> GeneralEntityAffiliatesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get affiliated entities and subsidiaries for any entity. Returns parent companies, subsidiaries, and related entities with ownership information. +
+
+
+
+ +#### 🔌 Usage + +
@@ -3096,12 +4261,17 @@ Search across all entity types (companies, people, investors, funds, deals, etc. ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.general.entity_affiliates( + entity_id="openai", ) -client.general.search() ```
@@ -3117,7 +4287,264 @@ client.general.search()
-**limit:** `typing.Optional[int]` — Maximum results +**entity_id:** `str` — Entity ID (any type) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + + + +
+ +
client.general.entity_news(...) -> GeneralEntityNewsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get recent news articles and mentions for any entity. Returns article titles, URLs, sources, publication dates, and snippets. Useful for tracking announcements, funding news, and company updates. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.general.entity_news( + entity_id="openai", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**entity_id:** `str` — Entity ID (any type) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Companies +
client.companies.search(...) -> CompaniesSearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Search for companies by name or natural language description. + +## Search Modes + +- **Direct lookup**: Short queries like `Stripe` or `OpenAI` resolve to a single company match +- **Natural language search**: Longer queries like `AI startups in San Francisco raising Series B` return up to 5 matching companies with surface-level data + +The endpoint auto-detects which mode to use based on the query. + +## Response Fields + +Each result includes: name, website, description, employee_count, industry, location, founded, size, total_funding_raised, latest_funding_stage, tags, and linkedin_url. + +Use the entity_id or company identifier from results to call detail endpoints (bio, financing, investors, full). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.companies.search( + q="OpenAI", + limit=10, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — Company name, domain, or natural language query (e.g. 'AI startups in San Francisco raising Series B') + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results (default 5) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.companies.full(...) -> CompaniesFullResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get the complete company record with ALL available data fields. + +Returns everything: base profile, funding details with amounts and investors, employee analytics and growth rates, executive changes, office locations, job postings, industry classifications, subsidiaries, and more. + +Use this after identifying a company via search to get the full picture. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.companies.full( + company_id="anthropic.com", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3137,7 +4564,7 @@ client.general.search()
-
client.general.search_shared() -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.bio(...) -> CompaniesBioResponse
@@ -3149,7 +4576,7 @@ client.general.search()
-Search for entities shared across your organization. Returns entities that multiple team members have accessed or tagged. Useful for discovering commonly referenced companies, investors, or people within your team. +Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data.
@@ -3165,12 +4592,17 @@ Search for entities shared across your organization. Returns entities that multi ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.companies.bio( + company_id="019cb8ac-adee-749e-a75e-a1c236f20f72", ) -client.general.search_shared() ``` @@ -3186,6 +4618,14 @@ client.general.search_shared()
+**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -3198,7 +4638,7 @@ client.general.search_shared()
-
client.general.entity_people(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.financials(...) -> CompaniesFinancialsResponse
@@ -3210,7 +4650,7 @@ client.general.search_shared()
-Get people associated with any entity (company employees, fund team members, investor partners, etc.). Returns names, titles, and LinkedIn profiles for key people. +Get financial statements for public companies including revenue, net income, assets, and liabilities. Returns most recent fiscal year data or specific year if requested.
@@ -3226,13 +4666,16 @@ Get people associated with any entity (company employees, fund team members, inv ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.general.entity_people( - entity_id="openai", + +client.companies.financials( + company_id="ody_co_sample_msft", ) ``` @@ -3249,7 +4692,15 @@ client.general.entity_people(
-**entity_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + +
+
+ +
+
+ +**fiscal_year:** `typing.Optional[int]` — Fiscal year (default: most recent)
@@ -3269,7 +4720,7 @@ client.general.entity_people(
-
client.general.entity_locations(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.financials_recent(...) -> CompaniesFinancialsRecentResponse
@@ -3281,7 +4732,7 @@ client.general.entity_people(
-Get office locations for any entity. Returns headquarters and branch office addresses with city, state, country, and full address details. +Get most recent financial statements for public companies. Convenience endpoint that automatically returns the latest available fiscal year data.
@@ -3297,13 +4748,16 @@ Get office locations for any entity. Returns headquarters and branch office addr ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.general.entity_locations( - entity_id="openai", + +client.companies.financials_recent( + company_id="openai.com", ) ``` @@ -3320,7 +4774,7 @@ client.general.entity_locations(
-**entity_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3340,7 +4794,7 @@ client.general.entity_locations(
-
client.general.entity_affiliates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.financing_recent(...) -> CompaniesFinancingRecentResponse
@@ -3352,7 +4806,7 @@ client.general.entity_locations(
-Get affiliated entities and subsidiaries for any entity. Returns parent companies, subsidiaries, and related entities with ownership information. +Get most recent funding rounds including round type, amount raised, investors, and valuation. Returns detailed information about the latest equity financing event.
@@ -3368,13 +4822,16 @@ Get affiliated entities and subsidiaries for any entity. Returns parent companie ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.general.entity_affiliates( - entity_id="openai", + +client.companies.financing_recent( + company_id="openai.com", ) ``` @@ -3391,7 +4848,7 @@ client.general.entity_affiliates(
-**entity_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3411,7 +4868,7 @@ client.general.entity_affiliates(
-
client.general.entity_news(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.active_investors(...) -> CompaniesActiveInvestorsResponse
@@ -3423,7 +4880,7 @@ client.general.entity_affiliates(
-Get recent news articles and mentions for any entity. Returns article titles, URLs, sources, publication dates, and snippets. Useful for tracking announcements, funding news, and company updates. +Get current investors in the company from recent funding rounds. Returns investor names, types, and contact information.
@@ -3439,13 +4896,16 @@ Get recent news articles and mentions for any entity. Returns article titles, UR ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.general.entity_news( - entity_id="openai", + +client.companies.active_investors( + company_id="openai.com", ) ``` @@ -3462,7 +4922,7 @@ client.general.entity_news(
-**entity_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3482,7 +4942,7 @@ client.general.entity_news(
-
client.general.entity_updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.deals(...) -> CompaniesDealsResponse
@@ -3494,7 +4954,7 @@ client.general.entity_news(
-Get changelog of data updates for any entity. Returns history of changes to the entity's profile, tracking when information was added or modified. Useful for monitoring data freshness. +Get all deals for a company including funding rounds and acquisitions. Each deal includes the deal type, round name, investors involved, amount raised, and status. Funding rounds are sourced from investor data, while acquisitions are sourced from M&A records.
@@ -3510,13 +4970,16 @@ Get changelog of data updates for any entity. Returns history of changes to the ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.general.entity_updates( - entity_id="openai", + +client.companies.deals( + company_id="openai.com", ) ``` @@ -3533,7 +4996,7 @@ client.general.entity_updates(
-**entity_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3553,8 +5016,7 @@ client.general.entity_updates(
-## Companies -
client.companies.search() -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.service_providers(...) -> CompaniesServiceProvidersResponse
@@ -3566,7 +5028,7 @@ client.general.entity_updates(
-Search for companies by name, industry, or location. Returns matching company profiles with employee count, industry classification, founding date, and headquarters. Use this to find company entity IDs for detailed lookups. +Get service providers working with the company including legal counsel, accounting firms, and consultants. Returns firm names, service types, and engagement information.
@@ -3582,12 +5044,17 @@ Search for companies by name, industry, or location. Returns matching company pr ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.companies.service_providers( + company_id="openai.com", ) -client.companies.search() ``` @@ -3603,6 +5070,14 @@ client.companies.search()
+**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -3615,7 +5090,7 @@ client.companies.search()
-
client.companies.bio(...) -> AsyncHttpResponse[typing.Any] +
client.companies.service_providers_deal(...) -> CompaniesServiceProvidersDealResponse
@@ -3627,7 +5102,7 @@ client.companies.search()
-Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. +Get service providers involved in specific financing deals including investment bankers, legal advisors, and financial consultants. Returns provider details specific to fundraising transactions.
@@ -3643,13 +5118,16 @@ Get comprehensive company profile including description, founding date, headquar ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.bio( - company_id="019cb8ac-adee-749e-a75e-a1c236f20f72", + +client.companies.service_providers_deal( + company_id="openai.com", ) ``` @@ -3666,7 +5144,7 @@ client.companies.bio(
-**company_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3686,7 +5164,7 @@ client.companies.bio(
-
client.companies.industries(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.debt_financing_recent(...) -> CompaniesDebtFinancingRecentResponse
@@ -3698,7 +5176,7 @@ client.companies.bio(
-Get industry classifications including NAICS codes, SIC codes, and industry descriptions. Useful for filtering companies by vertical or sector. +Get most recent debt financing rounds including venture debt, credit lines, and loans. Returns lender information, amounts, and terms.
@@ -3714,12 +5192,15 @@ Get industry classifications including NAICS codes, SIC codes, and industry desc ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.industries( + +client.companies.debt_financing_recent( company_id="openai.com", ) @@ -3737,7 +5218,7 @@ client.companies.industries(
-**company_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3757,7 +5238,7 @@ client.companies.industries(
-
client.companies.financials(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.similar(...) -> CompaniesSimilarResponse
@@ -3769,7 +5250,7 @@ client.companies.industries(
-Get financial statements for public companies including revenue, net income, assets, and liabilities. Returns most recent fiscal year data or specific year if requested. +Get companies similar to this one based on industry, size, and business model. Returns competitor and peer company information with similarity scores.
@@ -3785,13 +5266,16 @@ Get financial statements for public companies including revenue, net income, ass ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.financials( - company_id="ody_co_sample_msft", + +client.companies.similar( + company_id="openai.com", ) ``` @@ -3808,7 +5292,15 @@ client.companies.financials(
-**company_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10)
@@ -3828,7 +5320,7 @@ client.companies.financials(
-
client.companies.financials_recent(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.companies.vc_exit_predictions(...)
@@ -3840,7 +5332,7 @@ client.companies.financials(
-Get most recent financial statements for public companies. Convenience endpoint that automatically returns the latest available fiscal year data. +**Coming Soon** - Get predicted likelihood and timeline for company exit events (IPO or acquisition).
@@ -3856,12 +5348,15 @@ Get most recent financial statements for public companies. Convenience endpoint ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.financials_recent( + +client.companies.vc_exit_predictions( company_id="openai.com", ) @@ -3879,7 +5374,7 @@ client.companies.financials_recent(
-**company_id:** `str` +**company_id:** `str` — Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID)
@@ -3899,7 +5394,8 @@ client.companies.financials_recent(
-
client.companies.financing_recent(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## People +
client.people.search(...) -> PeopleSearchResponse
@@ -3911,7 +5407,18 @@ client.companies.financials_recent(
-Get most recent funding rounds including round type, amount raised, investors, and valuation. Returns detailed information about the latest equity financing event. +Search for people by name, company, title, or natural language query. Returns LinkedIn profiles with rich metadata including professional headline, location, bio excerpt, follower count, and school. + +Use the returned `entity_id` with `/bio`, `/contact`, or `/education-work` to get full enrichment data. + +**Pagination:** Use `offset` and `limit` to page through results. Check `has_more` in the response to determine if more pages are available. + +**Large result sets:** Supports up to 500 results per request. For requests exceeding 100 results, the API automatically expands the search with query variations to discover more profiles, then deduplicates by LinkedIn URL. + +**Examples:** +- `?q=engineers at Anthropic in San Francisco&limit=20` +- `?q=Sam Altman&limit=1` +- `?q=senior data scientists&company=Stripe&location=New York&limit=50&offset=0`
@@ -3927,13 +5434,18 @@ Get most recent funding rounds including round type, amount raised, investors, a ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.financing_recent( - company_id="openai.com", + +client.people.search( + q="engineers at Anthropic in San Francisco", + limit=5, + offset=0, ) ``` @@ -3950,7 +5462,47 @@ client.companies.financing_recent(
-**company_id:** `str` +**q:** `str` — Person name or search query + +
+
+ +
+
+ +**company:** `typing.Optional[str]` — Filter by current company + +
+
+ +
+
+ +**title:** `typing.Optional[str]` — Filter by job title + +
+
+ +
+
+ +**location:** `typing.Optional[str]` — Filter by location + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results per page (1-500). For limits above 100, query expansion is used automatically. + +
+
+ +
+
+ +**offset:** `typing.Optional[int]` — Number of results to skip for pagination. Use with limit to page through results.
@@ -3970,7 +5522,7 @@ client.companies.financing_recent(
-
client.companies.active_investors(...) -> AsyncHttpResponse[typing.Any] +
client.people.bio(...) -> PeopleBioResponse
@@ -3982,7 +5534,7 @@ client.companies.financing_recent(
-Get current investors in the company from recent funding rounds. Returns investor names, types, and contact information. +Get complete person profile including bio, contact information (emails, phones, social profiles), work history (all positions with companies, titles, dates), and education (schools, degrees, fields of study). One API call returns everything. Use the entity_id from /people/search results.
@@ -3998,13 +5550,16 @@ Get current investors in the company from recent funding rounds. Returns investo ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.active_investors( - company_id="openai.com", + +client.people.bio( + person_id="019d886b-c9e6-745a-b91f-d7ece19a914c", ) ``` @@ -4021,7 +5576,7 @@ client.companies.active_investors(
-**company_id:** `str` +**person_id:** `str` — Person entity ID
@@ -4041,7 +5596,8 @@ client.companies.active_investors(
-
client.companies.all_investors(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## Deals +
client.deals.search(...) -> DealsSearchResponse
@@ -4053,7 +5609,7 @@ client.companies.active_investors(
-Get complete investor history including historical investors from all funding rounds. Returns comprehensive list of all investors who have participated in company financing. +Search for funding rounds and deals by company, deal type, amount range, or date. Returns matching transactions with deal type, amount, date, and participants. Use this to find deal entity IDs for detailed lookups.
@@ -4069,15 +5625,16 @@ Get complete investor history including historical investors from all funding ro ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.companies.all_investors( - company_id="openai.com", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.deals.search() + ```
@@ -4092,7 +5649,7 @@ client.companies.all_investors(
-**company_id:** `str` +**q:** `typing.Optional[str]` — Company name or deal keyword (e.g., 'OpenAI Series B')
@@ -4100,70 +5657,63 @@ client.companies.all_investors(
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. +**company:** `typing.Optional[str]` — Filter by company name or domain (e.g., 'OpenAI')
- - +
+
+**deal_type:** `typing.Optional[str]` — Filter by deal type (e.g., 'series_a', 'series_b', 'seed', 'ipo', 'acquisition', 'debt') +
-
-
client.companies.deals(...) -> AsyncHttpResponse[typing.Any]
-#### 📝 Description - -
-
+**min_amount:** `typing.Optional[float]` — Minimum deal amount + +
+
-Get all deals for a company including funding rounds and acquisitions. Each deal includes the deal type, round name, investors involved, amount raised, and status. Funding rounds are sourced from investor data, while acquisitions are sourced from M&A records. -
-
+**max_amount:** `typing.Optional[float]` — Maximum deal amount +
-#### 🔌 Usage -
+**start_date:** `typing.Optional[str]` — Start date (YYYY-MM-DD) + +
+
+
-```python -from runcaptain import Captain - -client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.companies.deals( - company_id="openai.com", -) - -``` -
-
+**end_date:** `typing.Optional[str]` — End date (YYYY-MM-DD) + -#### ⚙️ Parameters -
+**page:** `typing.Optional[int]` — Page number + +
+
+
-**company_id:** `str` +**page_size:** `typing.Optional[int]` — Results per page
@@ -4183,7 +5733,7 @@ client.companies.deals(
-
client.companies.service_providers(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.bio(...) -> DealsBioResponse
@@ -4195,7 +5745,7 @@ client.companies.deals(
-Get service providers working with the company including legal counsel, accounting firms, and consultants. Returns firm names, service types, and engagement information. +Get comprehensive deal information including amount, type, date, company, and investor participants. This is the primary endpoint for deal overview data.
@@ -4211,13 +5761,16 @@ Get service providers working with the company including legal counsel, accounti ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.service_providers( - company_id="openai.com", + +client.deals.bio( + id="deal_openai_0", ) ``` @@ -4234,7 +5787,7 @@ client.companies.service_providers(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4254,7 +5807,7 @@ client.companies.service_providers(
-
client.companies.service_providers_deal(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.investors(...) -> DealsInvestorsResponse
@@ -4266,7 +5819,7 @@ client.companies.service_providers(
-Get service providers involved in specific financing deals including investment bankers, legal advisors, and financial consultants. Returns provider details specific to fundraising transactions. +Get all investors participating in the deal including lead and follow-on investors. Returns investor names, roles, and investment amounts.
@@ -4282,13 +5835,16 @@ Get service providers involved in specific financing deals including investment ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.service_providers_deal( - company_id="openai.com", + +client.deals.investors( + id="deal_openai_0", ) ``` @@ -4305,7 +5861,7 @@ client.companies.service_providers_deal(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4325,7 +5881,7 @@ client.companies.service_providers_deal(
-
client.companies.debt_financing_recent(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.service_providers(...) -> DealsServiceProvidersResponse
@@ -4337,7 +5893,7 @@ client.companies.service_providers_deal(
-Get most recent debt financing rounds including venture debt, credit lines, and loans. Returns lender information, amounts, and terms. +Get service providers involved in the deal including legal counsel, investment bankers, and financial advisors. Returns firm names and service types.
@@ -4353,13 +5909,16 @@ Get most recent debt financing rounds including venture debt, credit lines, and ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.debt_financing_recent( - company_id="openai.com", + +client.deals.service_providers( + id="deal_openai_0", ) ``` @@ -4376,7 +5935,7 @@ client.companies.debt_financing_recent(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4396,7 +5955,7 @@ client.companies.debt_financing_recent(
-
client.companies.similar(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.valuation(...) -> DealsValuationResponse
@@ -4408,7 +5967,7 @@ client.companies.debt_financing_recent(
-Get companies similar to this one based on industry, size, and business model. Returns competitor and peer company information with similarity scores. +Get valuation information including pre-money and post-money valuations, equity percentage, and pricing details. Useful for understanding deal economics.
@@ -4424,13 +5983,16 @@ Get companies similar to this one based on industry, size, and business model. R ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.similar( - company_id="openai.com", + +client.deals.valuation( + id="deal_openai_0", ) ``` @@ -4447,7 +6009,7 @@ client.companies.similar(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4467,7 +6029,7 @@ client.companies.similar(
-
client.companies.social_analytics(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.stock_info(...) -> DealsStockInfoResponse
@@ -4479,7 +6041,7 @@ client.companies.similar(
-Get social media metrics including LinkedIn followers, Twitter engagement, and Facebook presence. Useful for measuring company online visibility and growth. +Get current stock price and market data for the company involved in this deal. Only applicable for public companies. Returns real-time stock quotes and market metrics.
@@ -4495,13 +6057,16 @@ Get social media metrics including LinkedIn followers, Twitter engagement, and F ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.social_analytics( - company_id="openai.com", + +client.deals.stock_info( + id="deal_openai_0", ) ``` @@ -4518,7 +6083,7 @@ client.companies.social_analytics(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4538,7 +6103,7 @@ client.companies.social_analytics(
-
client.companies.vc_exit_predictions(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.cap_table(...)
@@ -4550,7 +6115,7 @@ client.companies.social_analytics(
-Coming Soon: Get predicted likelihood and timeline for company exit events (IPO or acquisition). Will return ML-based exit probability scores and estimated exit valuation ranges when implemented. +**Coming Soon** - Get capitalization table showing ownership breakdown after the deal.
@@ -4566,13 +6131,16 @@ Coming Soon: Get predicted likelihood and timeline for company exit events (IPO ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.vc_exit_predictions( - company_id="openai.com", + +client.deals.cap_table( + id="deal_openai_0", ) ``` @@ -4589,7 +6157,7 @@ client.companies.vc_exit_predictions(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4609,7 +6177,7 @@ client.companies.vc_exit_predictions(
-
client.companies.updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.tranche(...)
@@ -4621,7 +6189,7 @@ client.companies.vc_exit_predictions(
-Get changelog of updates to company profile data. Returns history of changes to company information with timestamps and modified fields. +**Coming Soon** - Get information about deal tranches and payment schedules for structured financing.
@@ -4637,13 +6205,16 @@ Get changelog of updates to company profile data. Returns history of changes to ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.companies.updates( - company_id="openai.com", + +client.deals.tranche( + id="deal_openai_0", ) ``` @@ -4660,7 +6231,7 @@ client.companies.updates(
-**company_id:** `str` +**id:** `str` — Deal entity ID
@@ -4680,8 +6251,7 @@ client.companies.updates(
-## People -
client.people.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.debt_lenders(...) -> DealsDebtLendersResponse
@@ -4693,7 +6263,7 @@ client.companies.updates(
-Search for professionals by name, company, title, or location. Returns matching profiles with current position, company, and LinkedIn URL. Use this to find person entity IDs for detailed lookups. +Get lenders participating in debt financing deals. Returns lender names, amounts, and terms for venture debt and credit facilities.
@@ -4709,44 +6279,33 @@ Search for professionals by name, company, title, or location. Returns matching ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.people.search() - -``` - -
- - -#### ⚙️ Parameters - -
-
- -
-
+client.deals.debt_lenders( + id="deal_openai_0", +) -**title:** `typing.Optional[str]` — Filter by job title - +``` +
+
+#### ⚙️ Parameters +
-**location:** `typing.Optional[str]` — Filter by location - -
-
-
-**limit:** `typing.Optional[int]` — Maximum results +**id:** `str` — Deal entity ID
@@ -4766,7 +6325,7 @@ client.people.search()
-
client.people.bio(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.deals.multiples(...)
@@ -4778,7 +6337,7 @@ client.people.search()
-Get comprehensive person profile including headline, summary, current company, location, and social profiles. This is the primary endpoint for person overview data. +**Coming Soon** - Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics.
@@ -4794,13 +6353,16 @@ Get comprehensive person profile including headline, summary, current company, l ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.people.bio( - person_id="ody_pe_019cb8ac-f123-749e-a75e-e5f669g53c05", + +client.deals.multiples( + id="deal_openai_0", ) ``` @@ -4817,7 +6379,7 @@ client.people.bio(
-**person_id:** `str` +**id:** `str` — Deal entity ID
@@ -4837,7 +6399,8 @@ client.people.bio(
-
client.people.contact(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## Investors +
client.investors.search(...) -> InvestorsSearchResponse
@@ -4849,7 +6412,7 @@ client.people.bio(
-Get contact information including email addresses, phone numbers, and social media profiles. Returns verified contact details for outreach. +Search for venture capital firms, angel investors, and institutional investors by name. Returns matching investor profiles with investment focus, portfolio size, and notable investments. Use this to find investor entity IDs for detailed lookups.
@@ -4865,15 +6428,16 @@ Get contact information including email addresses, phone numbers, and social med ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.people.contact( - person_id="linkedin.com/in/samaltman", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.investors.search() + ```
@@ -4888,7 +6452,31 @@ client.people.contact(
-**person_id:** `str` +**q:** `typing.Optional[str]` — Investor name or keyword (e.g., 'Sequoia Capital') + +
+
+ +
+
+ +**investor_type:** `typing.Optional[str]` — Filter by investor type + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number + +
+
+ +
+
+ +**page_size:** `typing.Optional[int]` — Results per page
@@ -4908,7 +6496,7 @@ client.people.contact(
-
client.people.education_work(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.bio(...) -> InvestorsBioResponse
@@ -4920,7 +6508,7 @@ client.people.contact(
-Get complete professional history including work experience and education. Returns job positions with companies, titles, dates, and degree information with schools and majors. +Get comprehensive investor profile including description, investment thesis, stage focus, sector focus, and notable portfolio companies. This is the primary endpoint for investor overview data.
@@ -4936,13 +6524,16 @@ Get complete professional history including work experience and education. Retur ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.people.education_work( - person_id="linkedin.com/in/samaltman", + +client.investors.bio( + id="ody_inv_019cb8ac-g789-749e-a75e-h7i880j64d16", ) ``` @@ -4959,7 +6550,7 @@ client.people.education_work(
-**person_id:** `str` +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -4979,7 +6570,7 @@ client.people.education_work(
-
client.people.updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.active_investments(...) -> InvestorsActiveInvestmentsResponse
@@ -4991,7 +6582,9 @@ client.people.education_work(
-Get changelog of updates to person profile data. Returns history of career moves, title changes, and profile updates with timestamps. +Get current portfolio companies that the investor has active positions in. Returns company names, investment dates, and current status. + +Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages.
@@ -5007,13 +6600,16 @@ Get changelog of updates to person profile data. Returns history of career moves ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.people.updates( - person_id="linkedin.com/in/samaltman", + +client.investors.active_investments( + id="deal_openai_0", ) ``` @@ -5030,7 +6626,23 @@ client.people.updates(
-**person_id:** `str` +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number for pagination (default: 1) + +
+
+ +
+
+ +**page_size:** `typing.Optional[int]` — Results per page (default: 50, max: 1000)
@@ -5050,8 +6662,7 @@ client.people.updates(
-## Deals -
client.deals.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.preferences(...) -> InvestorsPreferencesResponse
@@ -5063,7 +6674,7 @@ client.people.updates(
-Search for funding rounds and deals by company, deal type, amount range, or date. Returns matching transactions with deal type, amount, date, and participants. Use this to find deal entity IDs for detailed lookups. +Get investment preferences including stage focus, sector preferences, geography, and typical check size. Useful for determining investment fit.
@@ -5079,12 +6690,17 @@ Search for funding rounds and deals by company, deal type, amount range, or date ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.investors.preferences( + id="deal_openai_0", ) -client.deals.search() ``` @@ -5100,7 +6716,7 @@ client.deals.search()
-**deal_type:** `typing.Optional[str]` — Filter by deal type +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -5108,47 +6724,73 @@ client.deals.search()
-**min_amount:** `typing.Optional[float]` — Minimum deal amount +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+ +
-
-
-**max_amount:** `typing.Optional[float]` — Maximum deal amount -
+
+
client.investors.funds(...) -> InvestorsFundsResponse
-**start_date:** `typing.Optional[str]` — Start date (YYYY-MM-DD) - -
-
+#### 📝 Description
-**end_date:** `typing.Optional[str]` — End date (YYYY-MM-DD) - +
+
+ +Get all funds managed by the investor including fund names, sizes, vintage years, and status. Returns complete fund portfolio for multi-fund investors. +
+
+#### 🔌 Usage +
-**page:** `typing.Optional[int]` — Page number - +
+
+ +```python +from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment + +client = Captain( + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.investors.funds( + id="deal_openai_0", +) + +``` +
+
+#### ⚙️ Parameters +
-**page_size:** `typing.Optional[int]` — Results per page +
+
+ +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -5168,7 +6810,7 @@ client.deals.search()
-
client.deals.bio(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.funds_latest(...) -> InvestorsFundsLatestResponse
@@ -5180,7 +6822,7 @@ client.deals.search()
-Get comprehensive deal information including amount, type, date, company, and investor participants. This is the primary endpoint for deal overview data. +Get information about the investor's most recent fund including size, vintage year, and deployment status. Useful for understanding current investment capacity.
@@ -5196,12 +6838,15 @@ Get comprehensive deal information including amount, type, date, company, and in ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.bio( + +client.investors.funds_latest( id="deal_openai_0", ) @@ -5219,7 +6864,7 @@ client.deals.bio(
-**id:** `str` +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -5239,7 +6884,7 @@ client.deals.bio(
-
client.deals.detailed(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.board_seats(...) -> InvestorsBoardSeatsResponse
@@ -5251,7 +6896,7 @@ client.deals.bio(
-Get comprehensive deal data combining bio, investors, valuation, and terms in a single response. Use this for complete deal intelligence without multiple API calls. +Get board seats held by the investor's team members. Returns companies where investor partners serve on the board of directors.
@@ -5267,12 +6912,15 @@ Get comprehensive deal data combining bio, investors, valuation, and terms in a ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.detailed( + +client.investors.board_seats( id="deal_openai_0", ) @@ -5290,7 +6938,7 @@ client.deals.detailed(
-**id:** `str` +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -5310,7 +6958,7 @@ client.deals.detailed(
-
client.deals.investors(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.service_providers(...) -> InvestorsServiceProvidersResponse
@@ -5322,7 +6970,7 @@ client.deals.detailed(
-Get all investors participating in the deal including lead and follow-on investors. Returns investor names, roles, and investment amounts. +Get service providers used by the investor including legal counsel, fund administrators, and consultants. Returns firm names and service types.
@@ -5338,12 +6986,15 @@ Get all investors participating in the deal including lead and follow-on investo ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.investors( + +client.investors.service_providers( id="deal_openai_0", ) @@ -5361,7 +7012,7 @@ client.deals.investors(
-**id:** `str` +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -5381,7 +7032,7 @@ client.deals.investors(
-
client.deals.service_providers(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.investors.service_providers_deal(...) -> InvestorsServiceProvidersDealResponse
@@ -5393,7 +7044,7 @@ client.deals.investors(
-Get service providers involved in the deal including legal counsel, investment bankers, and financial advisors. Returns firm names and service types. +Get service providers involved in the investor's deal flow including transaction advisors and due diligence firms. Returns provider details specific to deal execution.
@@ -5409,12 +7060,15 @@ Get service providers involved in the deal including legal counsel, investment b ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.service_providers( + +client.investors.service_providers_deal( id="deal_openai_0", ) @@ -5432,7 +7086,7 @@ client.deals.service_providers(
-**id:** `str` +**id:** `str` — Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search
@@ -5452,7 +7106,8 @@ client.deals.service_providers(
-
client.deals.valuation(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## Funds +
client.funds.search(...) -> FundsSearchResponse
@@ -5464,7 +7119,7 @@ client.deals.service_providers(
-Get valuation information including pre-money and post-money valuations, equity percentage, and pricing details. Useful for understanding deal economics. +Search for venture capital and private equity funds by name. Returns matching fund profiles with size, vintage year, and investment focus. Use this to find fund entity IDs for detailed lookups.
@@ -5480,13 +7135,17 @@ Get valuation information including pre-money and post-money valuations, equity ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.valuation( - id="deal_openai_0", + +client.funds.search( + q="Sequoia Capital Fund", + limit=10, ) ``` @@ -5503,7 +7162,31 @@ client.deals.valuation(
-**id:** `str` +**q:** `str` — Fund name or keyword (e.g., 'Sequoia Capital Fund XIV') + +
+
+ +
+
+ +**fund_type:** `typing.Optional[str]` — Filter by fund type (e.g., 'venture', 'buyout', 'growth_equity', 'real_estate', 'debt') + +
+
+ +
+
+ +**vintage_year:** `typing.Optional[int]` — Filter by fund vintage year (e.g., 2023) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10)
@@ -5523,7 +7206,7 @@ client.deals.valuation(
-
client.deals.stock_info(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.bio(...) -> FundsBioResponse
@@ -5535,7 +7218,7 @@ client.deals.valuation(
-Get current stock price and market data for the company involved in this deal. Only applicable for public companies. Returns real-time stock quotes and market metrics. +Get comprehensive fund profile including fund size, vintage year, investment strategy, stage focus, and sector focus. This is the primary endpoint for fund overview data.
@@ -5551,13 +7234,16 @@ Get current stock price and market data for the company involved in this deal. O ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.stock_info( - id="deal_openai_0", + +client.funds.bio( + fund_id="sequoia", ) ``` @@ -5574,7 +7260,7 @@ client.deals.stock_info(
-**id:** `str` +**fund_id:** `str` — Fund entity ID
@@ -5594,7 +7280,7 @@ client.deals.stock_info(
-
client.deals.cap_table(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.performance(...)
@@ -5606,7 +7292,7 @@ client.deals.stock_info(
-Coming Soon: Get capitalization table showing ownership breakdown after the deal. Will return equity ownership percentages, share counts, and investor stakes when implemented. +**Coming Soon** - Get fund performance metrics including IRR, TVPI, DPI, and RVPI.
@@ -5622,13 +7308,16 @@ Coming Soon: Get capitalization table showing ownership breakdown after the deal ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.cap_table( - id="deal_openai_0", + +client.funds.performance( + fund_id="sequoia", ) ``` @@ -5645,7 +7334,7 @@ client.deals.cap_table(
-**id:** `str` +**fund_id:** `str` — Fund entity ID
@@ -5665,7 +7354,7 @@ client.deals.cap_table(
-
client.deals.tranche(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.active_investments(...) -> FundsActiveInvestmentsResponse
@@ -5677,7 +7366,9 @@ client.deals.cap_table(
-Coming Soon: Get information about deal tranches and payment schedules for structured financing. Will return tranche amounts, dates, and conditions when implemented. +Get current portfolio companies invested by this specific fund. Returns company names, investment amounts, and current status. + +Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages.
@@ -5693,13 +7384,16 @@ Coming Soon: Get information about deal tranches and payment schedules for struc ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.tranche( - id="deal_openai_0", + +client.funds.active_investments( + fund_id="sequoia", ) ``` @@ -5716,7 +7410,23 @@ client.deals.tranche(
-**id:** `str` +**fund_id:** `str` — Fund entity ID + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number for pagination (default: 1) + +
+
+ +
+
+ +**page_size:** `typing.Optional[int]` — Results per page (default: 50, max: 1000)
@@ -5736,7 +7446,7 @@ client.deals.tranche(
-
client.deals.debt_lenders(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.commitments(...)
@@ -5748,7 +7458,7 @@ client.deals.tranche(
-Get lenders participating in debt financing deals. Returns lender names, amounts, and terms for venture debt and credit facilities. +**Coming Soon** - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure.
@@ -5764,13 +7474,16 @@ Get lenders participating in debt financing deals. Returns lender names, amounts ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.debt_lenders( - id="deal_openai_0", + +client.funds.commitments( + fund_id="sequoia", ) ``` @@ -5787,7 +7500,7 @@ client.deals.debt_lenders(
-**id:** `str` +**fund_id:** `str` — Fund entity ID
@@ -5807,7 +7520,7 @@ client.deals.debt_lenders(
-
client.deals.multiples(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.cash_flows(...)
@@ -5819,7 +7532,7 @@ client.deals.debt_lenders(
-Coming Soon: Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Will return detailed valuation analysis when implemented. +**Coming Soon** - Get fund cash flow history including capital calls and distributions.
@@ -5835,13 +7548,16 @@ Coming Soon: Get valuation multiples for the deal including revenue multiple, EB ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.multiples( - id="deal_openai_0", + +client.funds.cash_flows( + fund_id="sequoia", ) ``` @@ -5858,7 +7574,7 @@ client.deals.multiples(
-**id:** `str` +**fund_id:** `str` — Fund entity ID
@@ -5878,7 +7594,7 @@ client.deals.multiples(
-
client.deals.updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.benchmark(...)
@@ -5890,7 +7606,7 @@ client.deals.multiples(
-Get changelog of updates to deal information. Returns history of changes to deal data with timestamps and modified fields. +**Coming Soon** - Get fund performance compared to industry benchmarks and peer funds.
@@ -5906,13 +7622,16 @@ Get changelog of updates to deal information. Returns history of changes to deal ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.deals.updates( - id="deal_openai_0", + +client.funds.benchmark( + fund_id="sequoia", ) ``` @@ -5929,7 +7648,7 @@ client.deals.updates(
-**id:** `str` +**fund_id:** `str` — Fund entity ID
@@ -5949,8 +7668,7 @@ client.deals.updates(
-## Investors -
client.investors.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.funds.preferences(...)
@@ -5962,7 +7680,7 @@ client.deals.updates(
-Search for venture capital firms, angel investors, and institutional investors by name. Returns matching investor profiles with investment focus, portfolio size, and notable investments. Use this to find investor entity IDs for detailed lookups. +**Coming Soon** - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges.
@@ -5978,12 +7696,17 @@ Search for venture capital firms, angel investors, and institutional investors b ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.funds.preferences( + fund_id="sequoia", ) -client.investors.search() ``` @@ -5999,15 +7722,7 @@ client.investors.search()
-**page:** `typing.Optional[int]` — Page number - -
-
- -
-
- -**page_size:** `typing.Optional[int]` — Results per page +**fund_id:** `str` — Fund entity ID
@@ -6027,7 +7742,8 @@ client.investors.search()
-
client.investors.bio(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## LimitedPartners +
client.limited_partners.lps_search(...) -> LpsSearchResponse
@@ -6039,7 +7755,7 @@ client.investors.search()
-Get comprehensive investor profile including description, investment thesis, stage focus, sector focus, and notable portfolio companies. This is the primary endpoint for investor overview data. +Search for institutional limited partners including pension funds, endowments, and family offices. Returns matching LP profiles with total commitments and fund relationships.
@@ -6055,30 +7771,50 @@ Get comprehensive investor profile including description, investment thesis, sta ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.bio( - id="ody_inv_019cb8ac-g789-749e-a75e-h7i880j64d16", + +client.limited_partners.lps_search( + q="CalPERS", + limit=10, ) -``` - -
+``` + + + + + +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — LP name or keyword (e.g., 'CalPERS') +
-#### ⚙️ Parameters -
+**lp_type:** `typing.Optional[str]` — Filter by LP type (e.g., 'pension_fund', 'endowment', 'family_office', 'sovereign_wealth', 'insurance') + +
+
+
-**id:** `str` +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10)
@@ -6098,7 +7834,7 @@ client.investors.bio(
-
client.investors.active_investments(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_bio(...) -> LpsBioResponse
@@ -6110,7 +7846,7 @@ client.investors.bio(
-Get current portfolio companies that the investor has active positions in. Returns company names, investment dates, and current status. +Get comprehensive limited partner profile including institution type, total assets under management, investment strategy, and notable fund commitments. This is the primary endpoint for LP overview data.
@@ -6126,13 +7862,16 @@ Get current portfolio companies that the investor has active positions in. Retur ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.active_investments( - id="deal_openai_0", + +client.limited_partners.lps_bio( + lp_id="calpers", ) ``` @@ -6149,7 +7888,7 @@ client.investors.active_investments(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6169,7 +7908,7 @@ client.investors.active_investments(
-
client.investors.all_investments(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_commitments_detailed(...)
@@ -6181,7 +7920,7 @@ client.investors.active_investments(
-Get complete investment history including current portfolio and exited positions. Returns all companies the investor has backed with investment details and outcomes. +**Coming Soon** - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio.
@@ -6197,13 +7936,16 @@ Get complete investment history including current portfolio and exited positions ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.all_investments( - id="deal_openai_0", + +client.limited_partners.lps_commitments_detailed( + lp_id="calpers", ) ``` @@ -6220,7 +7962,7 @@ client.investors.all_investments(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6240,7 +7982,7 @@ client.investors.all_investments(
-
client.investors.preferences(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_commitments_aggregates(...)
@@ -6252,7 +7994,7 @@ client.investors.all_investments(
-Get investment preferences including stage focus, sector preferences, geography, and typical check size. Useful for determining investment fit. +**Coming Soon** - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary.
@@ -6268,13 +8010,16 @@ Get investment preferences including stage focus, sector preferences, geography, ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.preferences( - id="deal_openai_0", + +client.limited_partners.lps_commitments_aggregates( + lp_id="calpers", ) ``` @@ -6291,7 +8036,7 @@ client.investors.preferences(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6311,7 +8056,7 @@ client.investors.preferences(
-
client.investors.funds(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_allocations_target(...)
@@ -6323,7 +8068,7 @@ client.investors.preferences(
-Get all funds managed by the investor including fund names, sizes, vintage years, and status. Returns complete fund portfolio for multi-fund investors. +**Coming Soon** - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix.
@@ -6339,13 +8084,16 @@ Get all funds managed by the investor including fund names, sizes, vintage years ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.funds( - id="deal_openai_0", + +client.limited_partners.lps_allocations_target( + lp_id="calpers", ) ``` @@ -6362,7 +8110,7 @@ client.investors.funds(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6382,7 +8130,7 @@ client.investors.funds(
-
client.investors.funds_latest(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_allocations_actual(...)
@@ -6394,7 +8142,7 @@ client.investors.funds(
-Get information about the investor's most recent fund including size, vintage year, and deployment status. Useful for understanding current investment capacity. +**Coming Soon** - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets.
@@ -6410,13 +8158,16 @@ Get information about the investor's most recent fund including size, vintage ye ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.funds_latest( - id="deal_openai_0", + +client.limited_partners.lps_allocations_actual( + lp_id="calpers", ) ``` @@ -6433,7 +8184,7 @@ client.investors.funds_latest(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6453,7 +8204,7 @@ client.investors.funds_latest(
-
client.investors.board_seats(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_commitment_preferences(...)
@@ -6465,7 +8216,7 @@ client.investors.funds_latest(
-Get board seats held by the investor's team members. Returns companies where investor partners serve on the board of directors. +**Coming Soon** - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting.
@@ -6481,13 +8232,16 @@ Get board seats held by the investor's team members. Returns companies where inv ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.board_seats( - id="deal_openai_0", + +client.limited_partners.lps_commitment_preferences( + lp_id="calpers", ) ``` @@ -6504,7 +8258,7 @@ client.investors.board_seats(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6524,7 +8278,7 @@ client.investors.board_seats(
-
client.investors.service_providers(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.limited_partners.lps_service_providers(...)
@@ -6536,7 +8290,7 @@ client.investors.board_seats(
-Get service providers used by the investor including legal counsel, fund administrators, and consultants. Returns firm names and service types. +**Coming Soon** - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types.
@@ -6552,13 +8306,16 @@ Get service providers used by the investor including legal counsel, fund adminis ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.service_providers( - id="deal_openai_0", + +client.limited_partners.lps_service_providers( + lp_id="calpers", ) ``` @@ -6575,7 +8332,7 @@ client.investors.service_providers(
-**id:** `str` +**lp_id:** `str` — LP entity ID
@@ -6595,7 +8352,8 @@ client.investors.service_providers(
-
client.investors.service_providers_deal(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## ServiceProviders +
client.service_providers.search(...) -> ServiceProvidersSearchResponse
@@ -6607,7 +8365,7 @@ client.investors.service_providers(
-Get service providers involved in the investor's deal flow including transaction advisors and due diligence firms. Returns provider details specific to deal execution. +Search for service providers including law firms, accounting firms, investment banks, and consultants. Returns matching provider profiles with specializations and notable clients.
@@ -6623,13 +8381,17 @@ Get service providers involved in the investor's deal flow including transaction ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.service_providers_deal( - id="deal_openai_0", + +client.service_providers.search( + q="Wilson Sonsini", + limit=10, ) ``` @@ -6646,7 +8408,23 @@ client.investors.service_providers_deal(
-**id:** `str` +**q:** `str` — Provider name or keyword (e.g., 'Wilson Sonsini') + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10) + +
+
+ +
+
+ +**provider_type:** `typing.Optional[str]` — Filter by provider type (e.g., 'law', 'accounting', 'investment_bank', 'consulting')
@@ -6666,7 +8444,7 @@ client.investors.service_providers_deal(
-
client.investors.updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.service_providers.bio(...) -> ServiceProvidersBioResponse
@@ -6678,7 +8456,7 @@ client.investors.service_providers_deal(
-Get changelog of updates to investor profile data. Returns history of changes including new investments, fund raises, and team changes with timestamps. +Get comprehensive service provider profile including firm description, practice areas, office locations, and notable work. This is the primary endpoint for provider overview data.
@@ -6694,13 +8472,16 @@ Get changelog of updates to investor profile data. Returns history of changes in ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.investors.updates( - id="deal_openai_0", + +client.service_providers.bio( + provider_id="wilson-sonsini", ) ``` @@ -6717,7 +8498,7 @@ client.investors.updates(
-**id:** `str` +**provider_id:** `str` — Service provider entity ID
@@ -6737,8 +8518,7 @@ client.investors.updates(
-## Funds -
client.funds.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.service_providers.companies(...) -> ServiceProvidersCompaniesResponse
@@ -6750,7 +8530,7 @@ client.investors.updates(
-Search for venture capital and private equity funds by name. Returns matching fund profiles with size, vintage year, and investment focus. Use this to find fund entity IDs for detailed lookups. +Get companies that have engaged this service provider. Returns client list with engagement types and sectors served.
@@ -6766,12 +8546,17 @@ Search for venture capital and private equity funds by name. Returns matching fu ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.service_providers.companies( + provider_id="wilson-sonsini", ) -client.funds.search() ``` @@ -6787,15 +8572,7 @@ client.funds.search()
-**vintage_year:** `typing.Optional[int]` — Filter by vintage year - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Maximum results +**provider_id:** `str` — Service provider entity ID
@@ -6815,7 +8592,7 @@ client.funds.search()
-
client.funds.bio(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.service_providers.deals(...) -> ServiceProvidersDealsResponse
@@ -6827,7 +8604,7 @@ client.funds.search()
-Get comprehensive fund profile including fund size, vintage year, investment strategy, stage focus, and sector focus. This is the primary endpoint for fund overview data. +Get deals where this provider was involved as advisor, counsel, or banker. Returns transaction history with roles and deal values.
@@ -6843,13 +8620,16 @@ Get comprehensive fund profile including fund size, vintage year, investment str ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.bio( - fund_id="sequoia", + +client.service_providers.deals( + provider_id="wilson-sonsini", ) ``` @@ -6866,7 +8646,7 @@ client.funds.bio(
-**fund_id:** `str` +**provider_id:** `str` — Service provider entity ID
@@ -6886,7 +8666,7 @@ client.funds.bio(
-
client.funds.performance(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.service_providers.investors(...) -> ServiceProvidersInvestorsResponse
@@ -6898,7 +8678,7 @@ client.funds.bio(
-Coming Soon: Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Will return LP-level performance data when implemented with proprietary fund reporting. +Get investors that have engaged this service provider. Returns investor clients with engagement types and fund formation work.
@@ -6914,13 +8694,16 @@ Coming Soon: Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Wi ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.performance( - fund_id="sequoia", + +client.service_providers.investors( + provider_id="wilson-sonsini", ) ``` @@ -6937,7 +8720,7 @@ client.funds.performance(
-**fund_id:** `str` +**provider_id:** `str` — Service provider entity ID
@@ -6957,7 +8740,7 @@ client.funds.performance(
-
client.funds.active_investments(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.service_providers.funds(...) -> ServiceProvidersFundsResponse
@@ -6969,7 +8752,7 @@ client.funds.performance(
-Get current portfolio companies invested by this specific fund. Returns company names, investment amounts, and current status. +Get funds that have engaged this service provider. Returns fund formation and compliance work with engagement details.
@@ -6985,13 +8768,16 @@ Get current portfolio companies invested by this specific fund. Returns company ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.active_investments( - fund_id="sequoia", + +client.service_providers.funds( + provider_id="wilson-sonsini", ) ``` @@ -7008,7 +8794,7 @@ client.funds.active_investments(
-**fund_id:** `str` +**provider_id:** `str` — Service provider entity ID
@@ -7028,7 +8814,7 @@ client.funds.active_investments(
-
client.funds.all_investments(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.service_providers.limited_partners(...)
@@ -7040,7 +8826,7 @@ client.funds.active_investments(
-Get complete investment history for this specific fund including current and exited positions. Returns all companies backed by the fund with outcomes. +**Coming Soon** - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details.
@@ -7056,13 +8842,16 @@ Get complete investment history for this specific fund including current and exi ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.all_investments( - fund_id="sequoia", + +client.service_providers.limited_partners( + provider_id="wilson-sonsini", ) ``` @@ -7079,7 +8868,7 @@ client.funds.all_investments(
-**fund_id:** `str` +**provider_id:** `str` — Service provider entity ID
@@ -7099,7 +8888,8 @@ client.funds.all_investments(
-
client.funds.commitments(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## Patents +
client.patents.search(...) -> PatentsSearchResponse
@@ -7111,7 +8901,7 @@ client.funds.all_investments(
-Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. +Search for patents by title, inventor, assignee, or classification. Returns matching patents with patent numbers, titles, filing dates, and status.
@@ -7127,13 +8917,17 @@ Get limited partner commitments to the fund including LP names and commitment am ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.commitments( - fund_id="sequoia", + +client.patents.search( + q="machine learning", + limit=10, ) ``` @@ -7150,7 +8944,23 @@ client.funds.commitments(
-**fund_id:** `str` +**q:** `str` — Patent keyword, assignee, or inventor name + +
+
+ +
+
+ +**assignee:** `typing.Optional[str]` — Filter by assignee (company) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 10)
@@ -7170,7 +8980,7 @@ client.funds.commitments(
-
client.funds.cash_flows(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.patents.get_by_id(...) -> PatentsGetByIdResponse
@@ -7182,7 +8992,11 @@ client.funds.commitments(
-Coming Soon: Get fund cash flow history including capital calls and distributions. Will return quarterly cash flow statements when implemented with LP reporting data. +Get patent details by patent number. + +Uses Google Patents search engine to find structured patent data including title, abstract, assignee, inventors, filing date, and publication date. + +Returns a direct link to the patent on Google Patents.
@@ -7198,13 +9012,16 @@ Coming Soon: Get fund cash flow history including capital calls and distribution ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.cash_flows( - fund_id="sequoia", + +client.patents.get_by_id( + id="deal_openai_0", ) ``` @@ -7221,7 +9038,7 @@ client.funds.cash_flows(
-**fund_id:** `str` +**id:** `str` — Patent ID or number
@@ -7241,7 +9058,7 @@ client.funds.cash_flows(
-
client.funds.benchmark(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.patents.get_file(...) -> PatentsGetFileResponse
@@ -7253,7 +9070,7 @@ client.funds.cash_flows(
-Coming Soon: Get fund performance compared to industry benchmarks and peer funds. Will return quartile rankings and comparative metrics when implemented. +Download patent file wrapper or PDF document. Returns patent documentation and prosecution history files.
@@ -7269,13 +9086,16 @@ Coming Soon: Get fund performance compared to industry benchmarks and peer funds ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.benchmark( - fund_id="sequoia", + +client.patents.get_file( + entity_id="US11234567B2", ) ``` @@ -7292,7 +9112,7 @@ client.funds.benchmark(
-**fund_id:** `str` +**entity_id:** `str` — Patent entity ID
@@ -7312,7 +9132,8 @@ client.funds.benchmark(
-
client.funds.people(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## CreditAnalysis +
client.credit_analysis.news_search(...) -> CreditAnalysisNewsSearchResponse
@@ -7324,7 +9145,7 @@ client.funds.benchmark(
-Get investment team members for the fund including partners, principals, and associates. Returns names, titles, and LinkedIn profiles. +Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content.
@@ -7340,13 +9161,17 @@ Get investment team members for the fund including partners, principals, and ass ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.funds.people( - fund_id="sequoia", + +client.credit_analysis.news_search( + q="Ares Capital", + limit=10, ) ``` @@ -7363,7 +9188,7 @@ client.funds.people(
-**fund_id:** `str` +**q:** `str` — Search query for credit news (e.g., 'Tesla bond issuance')
@@ -7371,70 +9196,39 @@ client.funds.people(
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. +**category:** `typing.Optional[str]` — Filter by news category (e.g., 'bond_issuance', 'rating_change', 'default', 'restructuring')
- -
- - - - -
-
client.funds.preferences(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]
-#### 📝 Description - -
-
+**regions:** `typing.Optional[str]` — Filter by region (e.g., 'north_america', 'europe', 'asia') + +
+
-Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. -
-
+**start_date:** `typing.Optional[str]` — Start date (YYYY-MM-DD) +
-#### 🔌 Usage -
-
-
- -```python -from runcaptain import Captain - -client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.funds.preferences( - fund_id="sequoia", -) - -``` -
-
+**end_date:** `typing.Optional[str]` — End date (YYYY-MM-DD) +
-#### ⚙️ Parameters - -
-
-
-**fund_id:** `str` +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 20)
@@ -7454,7 +9248,7 @@ client.funds.preferences(
-
client.funds.updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.news_recent(...) -> CreditAnalysisNewsRecentResponse
@@ -7466,7 +9260,7 @@ client.funds.preferences(
-Get changelog of updates to fund profile data. Returns history of changes including new investments, exits, and team changes with timestamps. +Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days.
@@ -7482,15 +9276,16 @@ Get changelog of updates to fund profile data. Returns history of changes includ ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.funds.updates( - fund_id="sequoia", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.credit_analysis.news_recent() + ```
@@ -7505,7 +9300,7 @@ client.funds.updates(
-**fund_id:** `str` +**limit:** `typing.Optional[int]` — Maximum number of results to return (1-100, default: 20)
@@ -7525,8 +9320,7 @@ client.funds.updates(
-## LimitedPartners -
client.limited_partners.lps_search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.news_detail(...) -> CreditAnalysisNewsDetailResponse
@@ -7538,7 +9332,7 @@ client.funds.updates(
-Search for institutional limited partners including pension funds, endowments, and family offices. Returns matching LP profiles with total commitments and fund relationships. +Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis.
@@ -7554,12 +9348,17 @@ Search for institutional limited partners including pension funds, endowments, a ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.credit_analysis.news_detail( + news_id="abc123def456", ) -client.limited_partners.lps_search() ``` @@ -7575,7 +9374,7 @@ client.limited_partners.lps_search()
-**limit:** `typing.Optional[int]` — Maximum results +**news_id:** `str` — News article ID
@@ -7595,7 +9394,7 @@ client.limited_partners.lps_search()
-
client.limited_partners.lps_bio(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.news_attachment(...)
@@ -7607,7 +9406,7 @@ client.limited_partners.lps_search()
-Get comprehensive limited partner profile including institution type, total assets under management, investment strategy, and notable fund commitments. This is the primary endpoint for LP overview data. +**Coming Soon** - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files.
@@ -7623,13 +9422,16 @@ Get comprehensive limited partner profile including institution type, total asse ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.limited_partners.lps_bio( - lp_id="calpers", + +client.credit_analysis.news_attachment( + news_id="abc123def456", ) ``` @@ -7646,7 +9448,7 @@ client.limited_partners.lps_bio(
-**lp_id:** `str` +**news_id:** `str` — News article ID
@@ -7666,7 +9468,7 @@ client.limited_partners.lps_bio(
-
client.limited_partners.lps_commitments_detailed(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.news_bulk(...) -> CreditAnalysisNewsBulkResponse
@@ -7678,7 +9480,7 @@ client.limited_partners.lps_bio(
-Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. +Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID.
@@ -7694,13 +9496,19 @@ Get detailed fund commitments including specific fund names, commitment amounts, ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.limited_partners.lps_commitments_detailed( - lp_id="calpers", + +client.credit_analysis.news_bulk( + queries=[ + "Apple credit rating", + "Tesla debt analysis" + ], ) ``` @@ -7717,7 +9525,7 @@ client.limited_partners.lps_commitments_detailed(
-**lp_id:** `str` +**queries:** `typing.Optional[typing.List[str]]` — List of search queries
@@ -7737,7 +9545,7 @@ client.limited_partners.lps_commitments_detailed(
-
client.limited_partners.lps_commitments_aggregates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.list_bdcs() -> typing.Dict[str, typing.Any]
@@ -7749,7 +9557,7 @@ client.limited_partners.lps_commitments_detailed(
-Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. +List all tracked Business Development Companies (BDCs). BDCs are publicly traded private credit funds that disclose every loan quarterly in SEC 10-Q/10-K filings.
@@ -7765,15 +9573,16 @@ Get aggregated commitment statistics including total commitments by vintage year ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.limited_partners.lps_commitments_aggregates( - lp_id="calpers", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.credit_analysis.list_bdcs() + ```
@@ -7788,14 +9597,6 @@ client.limited_partners.lps_commitments_aggregates(
-**lp_id:** `str` - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -7808,7 +9609,7 @@ client.limited_partners.lps_commitments_aggregates(
-
client.limited_partners.lps_allocations_target(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.bdc_search(...) -> typing.Dict[str, typing.Any]
@@ -7820,7 +9621,9 @@ client.limited_partners.lps_commitments_aggregates(
-Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. +Search across all indexed BDC portfolios for a borrower, industry, or keyword. + +Searches loan-level data from ~50 BDC quarterly filings covering $150B+ in direct loans. Returns matching investments with terms (spread, seniority, maturity, fair value).
@@ -7836,13 +9639,18 @@ Get target allocation percentages by asset class, geography, and strategy. Retur ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.limited_partners.lps_allocations_target( - lp_id="calpers", + +client.credit_analysis.bdc_search( + q="software", + seniority="first_lien", + limit=10, ) ``` @@ -7859,7 +9667,31 @@ client.limited_partners.lps_allocations_target(
-**lp_id:** `str` +**q:** `str` — Search query - borrower name, industry, or keyword + +
+
+ +
+
+ +**seniority:** `typing.Optional[CreditAnalysisBdcSearchRequestSeniority]` — Filter by loan seniority + +
+
+ +
+
+ +**non_accrual_only:** `typing.Optional[bool]` — Only return defaulted (non-accrual) investments + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results
@@ -7879,7 +9711,7 @@ client.limited_partners.lps_allocations_target(
-
client.limited_partners.lps_allocations_actual(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.bdc_portfolio(...) -> typing.Dict[str, typing.Any]
@@ -7891,7 +9723,9 @@ client.limited_partners.lps_allocations_target(
-Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. +Full Schedule of Investments for a specific BDC, parsed from SEC 10-Q/10-K filings. + +Each investment includes: borrower name, industry, investment type, seniority, coupon, spread, reference rate, maturity, principal, fair value, and non-accrual status.
@@ -7907,13 +9741,16 @@ Get actual current allocation percentages by asset class, geography, and strateg ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.limited_partners.lps_allocations_actual( - lp_id="calpers", + +client.credit_analysis.bdc_portfolio( + ticker="ARCC", ) ``` @@ -7930,7 +9767,23 @@ client.limited_partners.lps_allocations_actual(
-**lp_id:** `str` +**ticker:** `str` — BDC ticker symbol (e.g., ARCC, BXSL, FSK) + +
+
+ +
+
+ +**quarter:** `typing.Optional[str]` — Quarter like '2025-Q1' - defaults to latest filing + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum investments to return
@@ -7950,7 +9803,7 @@ client.limited_partners.lps_allocations_actual(
-
client.limited_partners.lps_commitment_preferences(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.bdc_stats(...) -> typing.Dict[str, typing.Any]
@@ -7962,7 +9815,7 @@ client.limited_partners.lps_allocations_actual(
-Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. +Aggregate portfolio statistics for a BDC. Returns weighted average spread, non-accrual rate, seniority breakdown, and top industries.
@@ -7978,13 +9831,16 @@ Get commitment preferences including preferred fund sizes, vintage year focus, a ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.limited_partners.lps_commitment_preferences( - lp_id="calpers", + +client.credit_analysis.bdc_stats( + ticker="ARCC", ) ``` @@ -8001,7 +9857,7 @@ client.limited_partners.lps_commitment_preferences(
-**lp_id:** `str` +**ticker:** `str` — BDC ticker symbol
@@ -8021,7 +9877,7 @@ client.limited_partners.lps_commitment_preferences(
-
client.limited_partners.lps_service_providers(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.borrower_lookup(...) -> typing.Dict[str, typing.Any]
@@ -8033,7 +9889,9 @@ client.limited_partners.lps_commitment_preferences(
-Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. +Cross-BDC borrower lookup - find a company across all BDC portfolios. + +Returns every BDC that holds this borrower's debt, with position sizes, spreads, and valuations. Reveals syndication patterns and allows cross-lender credit deterioration monitoring.
@@ -8049,13 +9907,16 @@ Get service providers used by the LP including consultants, custodians, and lega ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.limited_partners.lps_service_providers( - lp_id="calpers", + +client.credit_analysis.borrower_lookup( + name="Finastra", ) ``` @@ -8072,7 +9933,7 @@ client.limited_partners.lps_service_providers(
-**lp_id:** `str` +**name:** `str` — Borrower/company name
@@ -8092,7 +9953,7 @@ client.limited_partners.lps_service_providers(
-
client.limited_partners.lps_updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.market_overview() -> typing.Dict[str, typing.Any]
@@ -8104,7 +9965,16 @@ client.limited_partners.lps_service_providers(
-Get changelog of updates to LP profile data. Returns history of changes including new commitments, policy changes, and team updates with timestamps. +Comprehensive private credit market snapshot from free public sources. + +Returns: +- **Lending standards** (SLOOS): Net % of banks tightening C&I loan standards +- **Credit spreads**: ICE BofA High Yield, BBB, BB, CCC spreads +- **Bank lending**: Total C&I loans outstanding +- **Interest rates**: 10Y Treasury, SOFR +- **Financial conditions**: St. Louis Financial Stress Index, Chicago NFCI + +Data sourced from Federal Reserve (FRED API), updated daily/weekly/quarterly.
@@ -8120,15 +9990,16 @@ Get changelog of updates to LP profile data. Returns history of changes includin ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.limited_partners.lps_updates( - lp_id="calpers", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.credit_analysis.market_overview() + ```
@@ -8143,14 +10014,6 @@ client.limited_partners.lps_updates(
-**lp_id:** `str` - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -8163,8 +10026,7 @@ client.limited_partners.lps_updates(
-## ServiceProviders -
client.service_providers.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.market_spreads(...) -> typing.Dict[str, typing.Any]
@@ -8176,7 +10038,7 @@ client.limited_partners.lps_updates(
-Search for service providers including law firms, accounting firms, investment banks, and consultants. Returns matching provider profiles with specializations and notable clients. +Current and historical credit spread data from ICE BofA indices. Returns High Yield, BBB, BB, and CCC spreads with historical trend.
@@ -8192,12 +10054,17 @@ Search for service providers including law firms, accounting firms, investment b ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.credit_analysis.market_spreads( + history=30, ) -client.service_providers.search() ``` @@ -8213,15 +10080,7 @@ client.service_providers.search()
-**limit:** `typing.Optional[int]` — Maximum results - -
-
- -
-
- -**provider_type:** `typing.Optional[str]` — Provider type filter +**history:** `typing.Optional[int]` — Number of historical data points
@@ -8241,7 +10100,7 @@ client.service_providers.search()
-
client.service_providers.bio(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.lending_standards(...) -> typing.Dict[str, typing.Any]
@@ -8253,7 +10112,7 @@ client.service_providers.search()
-Get comprehensive service provider profile including firm description, practice areas, office locations, and notable work. This is the primary endpoint for provider overview data. +Federal Reserve Senior Loan Officer Opinion Survey (SLOOS) data. Shows net % of banks tightening or easing C&I loan standards. Leading indicator for private credit market conditions.
@@ -8269,13 +10128,16 @@ Get comprehensive service provider profile including firm description, practice ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.service_providers.bio( - provider_id="wilson-sonsini", + +client.credit_analysis.lending_standards( + history=20, ) ``` @@ -8292,7 +10154,7 @@ client.service_providers.bio(
-**provider_id:** `str` +**history:** `typing.Optional[int]` — Number of quarterly data points
@@ -8312,7 +10174,7 @@ client.service_providers.bio(
-
client.service_providers.companies(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.funds_search(...) -> typing.Dict[str, typing.Any]
@@ -8324,7 +10186,7 @@ client.service_providers.bio(
-Get companies that have engaged this service provider. Returns client list with engagement types and sectors served. +Search private credit funds via SEC Form D filings. Form D is filed when private funds raise capital under Regulation D. Covers fund formations, managers, and capital raised.
@@ -8340,13 +10202,17 @@ Get companies that have engaged this service provider. Returns client list with ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.service_providers.companies( - provider_id="wilson-sonsini", + +client.credit_analysis.funds_search( + q="Ares", + strategy="direct_lending", ) ``` @@ -8363,7 +10229,23 @@ client.service_providers.companies(
-**provider_id:** `str` +**q:** `str` — Fund name, manager name, or keyword + +
+
+ +
+
+ +**strategy:** `typing.Optional[CreditAnalysisFundsSearchRequestStrategy]` — Filter by strategy + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results
@@ -8383,7 +10265,7 @@ client.service_providers.companies(
-
client.service_providers.deals(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.fund_formations(...) -> typing.Dict[str, typing.Any]
@@ -8395,7 +10277,7 @@ client.service_providers.companies(
-Get deals where this provider was involved as advisor, counsel, or banker. Returns transaction history with roles and deal values. +Recent private credit fund formations from SEC Form D filings. Shows new funds launching in the private credit space.
@@ -8411,13 +10293,16 @@ Get deals where this provider was involved as advisor, counsel, or banker. Retur ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.service_providers.deals( - provider_id="wilson-sonsini", + +client.credit_analysis.fund_formations( + days_back=90, ) ``` @@ -8434,7 +10319,15 @@ client.service_providers.deals(
-**provider_id:** `str` +**days_back:** `typing.Optional[int]` — Look back period in days + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results
@@ -8454,7 +10347,7 @@ client.service_providers.deals(
-
client.service_providers.investors(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.list_managers() -> typing.Dict[str, typing.Any]
@@ -8466,7 +10359,7 @@ client.service_providers.deals(
-Get investors that have engaged this service provider. Returns investor clients with engagement types and fund formation work. +List known private credit fund managers with their strategies and SEC CIK numbers.
@@ -8482,15 +10375,16 @@ Get investors that have engaged this service provider. Returns investor clients ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.service_providers.investors( - provider_id="wilson-sonsini", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.credit_analysis.list_managers() + ```
@@ -8505,14 +10399,6 @@ client.service_providers.investors(
-**provider_id:** `str` - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -8525,7 +10411,7 @@ client.service_providers.investors(
-
client.service_providers.funds(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.manager_detail(...) -> typing.Dict[str, typing.Any]
@@ -8537,7 +10423,7 @@ client.service_providers.investors(
-Get funds that have engaged this service provider. Returns fund formation and compliance work with engagement details. +Detailed information about a private credit fund manager. Combines SEC filing data with known manager intelligence. Returns filing history, fund count, strategy, and recent Form D filings.
@@ -8553,13 +10439,16 @@ Get funds that have engaged this service provider. Returns fund formation and co ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.service_providers.funds( - provider_id="wilson-sonsini", + +client.credit_analysis.manager_detail( + name="Ares Management", ) ``` @@ -8576,7 +10465,7 @@ client.service_providers.funds(
-**provider_id:** `str` +**name:** `str` — Manager name (e.g., 'Ares Management')
@@ -8596,7 +10485,7 @@ client.service_providers.funds(
-
client.service_providers.limited_partners(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.sba_search(...) -> typing.Dict[str, typing.Any]
@@ -8608,7 +10497,7 @@ client.service_providers.funds(
-Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. +Search SBA 7(a) loan data. Contains loan-level data with borrower, lender, terms, and performance for 100,000+ loans (FY2020-present).
@@ -8624,13 +10513,18 @@ Get limited partners that have engaged this service provider. Returns LP clients ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.service_providers.limited_partners( - provider_id="wilson-sonsini", + +client.credit_analysis.sba_search( + state="CA", + status="CHGOFF", + limit=5, ) ``` @@ -8647,7 +10541,7 @@ client.service_providers.limited_partners(
-**provider_id:** `str` +**borrower:** `typing.Optional[str]` — Borrower name (partial match)
@@ -8655,70 +10549,47 @@ client.service_providers.limited_partners(
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. +**lender:** `typing.Optional[str]` — Bank/lender name (partial match)
- -
+
+
+**state:** `typing.Optional[str]` — State code (e.g., CA, NY, TX) +
-
-
client.service_providers.updates(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]
-#### 📝 Description - -
-
+**naics:** `typing.Optional[str]` — NAICS code prefix (e.g., 5112 for software) + +
+
-Get changelog of updates to service provider profile data. Returns history of changes including new engagements, office openings, and team changes with timestamps. -
-
+**status:** `typing.Optional[CreditAnalysisSbaSearchRequestStatus]` — Loan status: CHGOFF (charged off), PIF (paid in full) +
-#### 🔌 Usage - -
-
-
-```python -from runcaptain import Captain - -client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.service_providers.updates( - provider_id="wilson-sonsini", -) - -``` -
-
+**min_amount:** `typing.Optional[float]` — Minimum gross approval amount ($) +
-#### ⚙️ Parameters -
-
-
- -**provider_id:** `str` +**limit:** `typing.Optional[int]` — Maximum results
@@ -8738,8 +10609,7 @@ client.service_providers.updates(
-## Patents -
client.patents.search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.sba_stats() -> typing.Dict[str, typing.Any]
@@ -8751,7 +10621,7 @@ client.service_providers.updates(
-Search for patents by title, inventor, assignee, or classification. Returns matching patents with patent numbers, titles, filing dates, and status. +Aggregate SBA 7(a) loan statistics. Returns default rates, average loan size, top states, and top industries from 100,000+ indexed loans.
@@ -8767,12 +10637,15 @@ Search for patents by title, inventor, assignee, or classification. Returns matc ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.patents.search() + +client.credit_analysis.sba_stats() ``` @@ -8788,14 +10661,6 @@ client.patents.search()
-**limit:** `typing.Optional[int]` — Maximum results - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -8808,7 +10673,7 @@ client.patents.search()
-
client.patents.get_by_id(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.agreements_search(...) -> typing.Dict[str, typing.Any]
@@ -8820,7 +10685,7 @@ client.patents.search()
-Get detailed patent information including abstract, claims, inventors, citations, and prosecution history. Returns comprehensive patent profile. +Search SEC 8-K filings for credit agreements. When public companies enter material credit facilities, they file the agreement as an exhibit to an 8-K.
@@ -8836,13 +10701,16 @@ Get detailed patent information including abstract, claims, inventors, citations ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.patents.get_by_id( - id="deal_openai_0", + +client.credit_analysis.agreements_search( + borrower="Dell", ) ``` @@ -8859,7 +10727,31 @@ client.patents.get_by_id(
-**id:** `str` +**borrower:** `typing.Optional[str]` — Borrower/company name + +
+
+ +
+
+ +**lender:** `typing.Optional[str]` — Lender/agent name + +
+
+ +
+
+ +**date_from:** `typing.Optional[str]` — Start date (YYYY-MM-DD) + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results
@@ -8879,7 +10771,7 @@ client.patents.get_by_id(
-
client.patents.get_file(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.agreement_detail(...) -> typing.Dict[str, typing.Any]
@@ -8891,7 +10783,7 @@ client.patents.get_by_id(
-Download patent file wrapper or PDF document. Returns patent documentation and prosecution history files. +Get the most recent credit agreement filing for a company. Locates the 8-K filing containing the credit agreement.
@@ -8907,13 +10799,16 @@ Download patent file wrapper or PDF document. Returns patent documentation and p ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.patents.get_file( - entity_id="US11234567B2", + +client.credit_analysis.agreement_detail( + company="Alloy", ) ``` @@ -8930,7 +10825,7 @@ client.patents.get_file(
-**entity_id:** `str` +**company:** `str` — Company name
@@ -8950,8 +10845,7 @@ client.patents.get_file(
-## CreditAnalysis -
client.credit_analysis.news_search(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.nport_funds() -> typing.Dict[str, typing.Any]
@@ -8963,7 +10857,7 @@ client.patents.get_file(
-Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. +List tracked private credit interval funds that file N-PORT. These filings contain loan-by-loan holdings.
@@ -8979,12 +10873,15 @@ Search for credit-related news and filings including bond issuances, credit rati ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.credit_analysis.news_search() + +client.credit_analysis.nport_funds() ``` @@ -9000,30 +10897,6 @@ client.credit_analysis.news_search()
-**start_date:** `typing.Optional[str]` — Start date (YYYY-MM-DD) - -
-
- -
-
- -**end_date:** `typing.Optional[str]` — End date (YYYY-MM-DD) - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Maximum results - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -9036,7 +10909,7 @@ client.credit_analysis.news_search()
-
client.credit_analysis.news_recent() -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.nport_search(...) -> typing.Dict[str, typing.Any]
@@ -9048,7 +10921,7 @@ client.credit_analysis.news_search()
-Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. +Search N-PORT filings for private credit holdings matching a borrower name or keyword.
@@ -9064,12 +10937,18 @@ Get most recent credit news and filings. Returns latest credit events, ratings, ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, +) + +client.credit_analysis.nport_search( + q="Brightstone Capital", + limit=10, ) -client.credit_analysis.news_recent() ``` @@ -9085,6 +10964,22 @@ client.credit_analysis.news_recent()
+**q:** `str` — Borrower name or keyword + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Maximum results + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -9097,7 +10992,7 @@ client.credit_analysis.news_recent()
-
client.credit_analysis.news_detail(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.nport_fund_filings(...) -> typing.Dict[str, typing.Any]
@@ -9109,7 +11004,7 @@ client.credit_analysis.news_recent()
-Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. +Get N-PORT filing list for a specific private credit interval fund.
@@ -9125,13 +11020,16 @@ Get detailed credit news article or filing including full text, metadata, and re ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.credit_analysis.news_detail( - news_id="abc123def456", + +client.credit_analysis.nport_fund_filings( + ticker="CCLFX", ) ``` @@ -9148,7 +11046,7 @@ client.credit_analysis.news_detail(
-**news_id:** `str` +**ticker:** `str` — Fund ticker (e.g., CCLFX, AFT)
@@ -9168,7 +11066,7 @@ client.credit_analysis.news_detail(
-
client.credit_analysis.news_attachment(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.relationships_search(...) -> typing.Dict[str, typing.Any]
@@ -9180,7 +11078,7 @@ client.credit_analysis.news_detail(
-Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. +Search lender-borrower relationships across BDC portfolios. Provide either `lender` (to find their borrowers) or `borrower` (to find their lenders).
@@ -9196,13 +11094,16 @@ Download attachment files associated with credit news including prospectuses, in ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.credit_analysis.news_attachment( - news_id="abc123def456", + +client.credit_analysis.relationships_search( + lender="Ares Capital", ) ``` @@ -9219,7 +11120,15 @@ client.credit_analysis.news_attachment(
-**news_id:** `str` +**lender:** `typing.Optional[str]` — Lender name - find their borrowers + +
+
+ +
+
+ +**borrower:** `typing.Optional[str]` — Borrower name - find their lenders
@@ -9239,7 +11148,7 @@ client.credit_analysis.news_attachment(
-
client.credit_analysis.news_bulk(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.credit_analysis.ucc_portals(...) -> typing.Dict[str, typing.Any]
@@ -9251,7 +11160,7 @@ client.credit_analysis.news_attachment(
-Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. +Get UCC filing search portal information for free state-level databases. Returns URLs for CA, NY, TX, FL, IL portals and a list of known major private credit lenders.
@@ -9267,15 +11176,16 @@ Retrieve multiple credit news articles by ID in a single request. Returns batch ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", -) -client.credit_analysis.news_bulk( - queries=["Apple credit rating", "Tesla debt analysis"], + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) +client.credit_analysis.ucc_portals() + ```
@@ -9290,7 +11200,7 @@ client.credit_analysis.news_bulk(
-**queries:** `typing.Optional[typing.Sequence[str]]` — List of search queries +**state:** `typing.Optional[str]` — State code (e.g., CA, NY). Omit for all states.
@@ -9310,8 +11220,8 @@ client.credit_analysis.news_bulk(
-## Fundamentals -
client.fundamentals.sandbox() -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +## SandboxData +
client.sandbox_data.fundamentals_sandbox() -> FundamentalsSandboxResponse
@@ -9339,12 +11249,15 @@ Get sample entities for testing and development. Returns mock company, person, i ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.fundamentals.sandbox() + +client.sandbox_data.fundamentals_sandbox() ```
@@ -9372,7 +11285,7 @@ client.fundamentals.sandbox()
-
client.fundamentals.lookup_tables() -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.sandbox_data.fundamentals_lookup_tables() -> FundamentalsLookupTablesResponse
@@ -9400,12 +11313,15 @@ Get available reference lookup tables including industry codes, country codes, a ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.fundamentals.lookup_tables() + +client.sandbox_data.fundamentals_lookup_tables() ```
@@ -9433,7 +11349,7 @@ client.fundamentals.lookup_tables()
-
client.fundamentals.lookup_table_values(...) -> AsyncHttpResponse[typing.Dict[str, typing.Any]] +
client.sandbox_data.fundamentals_lookup_table_values(...) -> FundamentalsLookupTableValuesResponse
@@ -9461,12 +11377,15 @@ Get values from a specific lookup table. Returns table data with codes, descript ```python from runcaptain import Captain +from runcaptain.environment import CaptainEnvironment client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", + key="", + organization_id="", + environment=CaptainEnvironment.DEFAULT, ) -client.fundamentals.lookup_table_values( + +client.sandbox_data.fundamentals_lookup_table_values( table_id="sectors", ) @@ -9484,7 +11403,7 @@ client.fundamentals.lookup_table_values(
-**table_id:** `str` +**table_id:** `str` — Lookup table ID
diff --git a/src/runcaptain/__init__.py b/src/runcaptain/__init__.py index 77fcf63..344ad13 100644 --- a/src/runcaptain/__init__.py +++ b/src/runcaptain/__init__.py @@ -14,6 +14,7 @@ CollectionItemV2, CollectionListResponseV2, CollectionResponseV2, + ConflictErrorBody, DatasetArticleResponse, DatasetSearchResponse, DatasetSearchResult, @@ -32,9 +33,12 @@ JobProgress, JobProgressCurrentStage, JobResult, + JobRollbackResponseV2, JobStatus, JobStatusResponseV2, JobStatusResponseV2JobType, + NotImplementedErrorBody, + NotImplementedErrorBodyDetails, QueryResponseV2, QueryStreamCompleteEvent, QueryStreamErrorEvent, @@ -47,10 +51,13 @@ QueryStreamTextEvent, QueryStreamToolEndEvent, QueryStreamToolStartEvent, - RelevantDocumentV2, + ScientificAskResponse, + ScientificSource, SearchResult, StandardResponseV2, TokenBalance, + ValidateParsingScriptResponseV2, + ValidateParsingScriptResponseV2ErrorType, ValidationError, ValidationErrorLocItem, ) @@ -62,6 +69,7 @@ NotImplementedError, ServiceUnavailableError, UnauthorizedError, + UnprocessableEntityError, ) from . import ( collections, @@ -69,7 +77,6 @@ credit_analysis, datasets, deals, - fundamentals, funds, general, indexing, @@ -79,16 +86,98 @@ patents, people, query, + sandbox_data, service_providers, ) from .client import AsyncCaptain, Captain from .collections import ChangeEnvironmentRequestV2NewEnvironment + from .companies import ( + CompaniesActiveInvestorsResponse, + CompaniesActiveInvestorsResponseInvestorsItem, + CompaniesBioResponse, + CompaniesBioResponseHeadquarters, + CompaniesBioResponseSocialProfiles, + CompaniesDealsResponse, + CompaniesDealsResponseDealsItem, + CompaniesDebtFinancingRecentResponse, + CompaniesDebtFinancingRecentResponseRound, + CompaniesFinancialsRecentResponse, + CompaniesFinancialsResponse, + CompaniesFinancingRecentResponse, + CompaniesFinancingRecentResponseRoundsItem, + CompaniesFinancingRecentResponseWebSourcesItem, + CompaniesFullResponse, + CompaniesFullResponseAffiliatedEntitiesItem, + CompaniesFullResponseFundingDetailsItem, + CompaniesFullResponseHeadquarters, + CompaniesFullResponseLocation, + CompaniesFullResponseSocialProfiles, + CompaniesSearchResponse, + CompaniesSearchResponseResultsItem, + CompaniesServiceProvidersDealResponse, + CompaniesServiceProvidersDealResponseServiceProvidersItem, + CompaniesServiceProvidersResponse, + CompaniesServiceProvidersResponseServiceProvidersItem, + CompaniesSimilarResponse, + CompaniesSimilarResponseCompetitorsItem, + ) + from .credit_analysis import ( + CreditAnalysisBdcSearchRequestSeniority, + CreditAnalysisFundsSearchRequestStrategy, + CreditAnalysisNewsBulkResponse, + CreditAnalysisNewsBulkResponseResultsItem, + CreditAnalysisNewsDetailResponse, + CreditAnalysisNewsRecentResponse, + CreditAnalysisNewsRecentResponseResultsItem, + CreditAnalysisNewsSearchResponse, + CreditAnalysisNewsSearchResponseResultsItem, + CreditAnalysisSbaSearchRequestStatus, + ) from .datasets import BatchSearchDatasetsResponse + from .deals import ( + DealsBioResponse, + DealsDebtLendersResponse, + DealsDebtLendersResponseLendersItem, + DealsInvestorsResponse, + DealsInvestorsResponseParticipantsItem, + DealsSearchResponse, + DealsSearchResponseResultsItem, + DealsServiceProvidersResponse, + DealsServiceProvidersResponseServiceProvidersItem, + DealsStockInfoResponse, + DealsValuationResponse, + DealsValuationResponseTerms, + ) from .environment import CaptainEnvironment + from .funds import ( + FundsActiveInvestmentsResponse, + FundsActiveInvestmentsResponseInvestmentsItem, + FundsBioResponse, + FundsBioResponseData, + FundsSearchResponse, + FundsSearchResponseResultsItem, + FundsSearchResponseResultsItemLocation, + ) + from .general import ( + GeneralEntityAffiliatesResponse, + GeneralEntityAffiliatesResponseAffiliatesItem, + GeneralEntityLocationsResponse, + GeneralEntityLocationsResponseLocationsItem, + GeneralEntityNewsResponse, + GeneralEntityNewsResponseNewsItem, + GeneralEntityPeopleResponse, + GeneralEntityPeopleResponsePeopleItem, + GeneralSearchRequestEntityType, + GeneralSearchResponse, + GeneralSearchResponseResultsItem, + GeneralSearchSharedResponse, + GeneralSearchSharedResponseResultsItem, + ) from .indexing import ( IndexAzureDirectoryRequestV2ProcessingType, IndexAzureFileRequestV2ProcessingType, IndexAzureRequestV2ProcessingType, + IndexFileV2RequestProcessingType, IndexGcsDirectoryRequestV2ProcessingType, IndexGcsFileRequestV2ProcessingType, IndexGcsRequestV2ProcessingType, @@ -103,6 +192,70 @@ IndexS3RequestV2ProcessingType, IndexUrlRequestV2ProcessingType, ) + from .investors import ( + InvestorsActiveInvestmentsResponse, + InvestorsActiveInvestmentsResponsePortfolioItem, + InvestorsBioResponse, + InvestorsBioResponseHeadquarters, + InvestorsBioResponseInvestorType, + InvestorsBoardSeatsResponse, + InvestorsBoardSeatsResponseBoardSeatsItem, + InvestorsFundsLatestResponse, + InvestorsFundsLatestResponseFundsItem, + InvestorsFundsLatestResponseLatestFund, + InvestorsFundsResponse, + InvestorsFundsResponseFundsItem, + InvestorsPreferencesResponse, + InvestorsSearchResponse, + InvestorsSearchResponseResultsItem, + InvestorsServiceProvidersDealResponse, + InvestorsServiceProvidersResponse, + ) + from .limited_partners import ( + LpsBioResponse, + LpsBioResponseData, + LpsSearchResponse, + LpsSearchResponseResultsItem, + LpsSearchResponseResultsItemLocation, + ) + from .patents import ( + PatentsGetByIdResponse, + PatentsGetFileResponse, + PatentsSearchResponse, + PatentsSearchResponsePatentsItem, + PatentsSearchResponseResultsItem, + ) + from .people import ( + PeopleBioResponse, + PeopleBioResponseCurrentCompany, + PeopleBioResponseLocation, + PeopleSearchResponse, + PeopleSearchResponseResultsItem, + ) + from .sandbox_data import ( + FundamentalsLookupTableValuesResponse, + FundamentalsLookupTableValuesResponseValuesItem, + FundamentalsLookupTablesResponse, + FundamentalsLookupTablesResponseTablesItem, + FundamentalsSandboxResponse, + FundamentalsSandboxResponseCompaniesItem, + FundamentalsSandboxResponseInvestorsItem, + FundamentalsSandboxResponsePeopleItem, + ) + from .service_providers import ( + ServiceProvidersBioResponse, + ServiceProvidersCompaniesResponse, + ServiceProvidersCompaniesResponseCompaniesItem, + ServiceProvidersDealsResponse, + ServiceProvidersDealsResponseDealsItem, + ServiceProvidersFundsResponse, + ServiceProvidersFundsResponseFundsItem, + ServiceProvidersInvestorsResponse, + ServiceProvidersInvestorsResponseInvestorsItem, + ServiceProvidersSearchResponse, + ServiceProvidersSearchResponseResultsItem, + ServiceProvidersSearchResponseResultsItemLocation, + ) from .version import __version__ _dynamic_imports: typing.Dict[str, str] = { "AsyncCaptain": ".client", @@ -118,10 +271,61 @@ "CollectionItemV2": ".types", "CollectionListResponseV2": ".types", "CollectionResponseV2": ".types", + "CompaniesActiveInvestorsResponse": ".companies", + "CompaniesActiveInvestorsResponseInvestorsItem": ".companies", + "CompaniesBioResponse": ".companies", + "CompaniesBioResponseHeadquarters": ".companies", + "CompaniesBioResponseSocialProfiles": ".companies", + "CompaniesDealsResponse": ".companies", + "CompaniesDealsResponseDealsItem": ".companies", + "CompaniesDebtFinancingRecentResponse": ".companies", + "CompaniesDebtFinancingRecentResponseRound": ".companies", + "CompaniesFinancialsRecentResponse": ".companies", + "CompaniesFinancialsResponse": ".companies", + "CompaniesFinancingRecentResponse": ".companies", + "CompaniesFinancingRecentResponseRoundsItem": ".companies", + "CompaniesFinancingRecentResponseWebSourcesItem": ".companies", + "CompaniesFullResponse": ".companies", + "CompaniesFullResponseAffiliatedEntitiesItem": ".companies", + "CompaniesFullResponseFundingDetailsItem": ".companies", + "CompaniesFullResponseHeadquarters": ".companies", + "CompaniesFullResponseLocation": ".companies", + "CompaniesFullResponseSocialProfiles": ".companies", + "CompaniesSearchResponse": ".companies", + "CompaniesSearchResponseResultsItem": ".companies", + "CompaniesServiceProvidersDealResponse": ".companies", + "CompaniesServiceProvidersDealResponseServiceProvidersItem": ".companies", + "CompaniesServiceProvidersResponse": ".companies", + "CompaniesServiceProvidersResponseServiceProvidersItem": ".companies", + "CompaniesSimilarResponse": ".companies", + "CompaniesSimilarResponseCompetitorsItem": ".companies", "ConflictError": ".errors", + "ConflictErrorBody": ".types", + "CreditAnalysisBdcSearchRequestSeniority": ".credit_analysis", + "CreditAnalysisFundsSearchRequestStrategy": ".credit_analysis", + "CreditAnalysisNewsBulkResponse": ".credit_analysis", + "CreditAnalysisNewsBulkResponseResultsItem": ".credit_analysis", + "CreditAnalysisNewsDetailResponse": ".credit_analysis", + "CreditAnalysisNewsRecentResponse": ".credit_analysis", + "CreditAnalysisNewsRecentResponseResultsItem": ".credit_analysis", + "CreditAnalysisNewsSearchResponse": ".credit_analysis", + "CreditAnalysisNewsSearchResponseResultsItem": ".credit_analysis", + "CreditAnalysisSbaSearchRequestStatus": ".credit_analysis", "DatasetArticleResponse": ".types", "DatasetSearchResponse": ".types", "DatasetSearchResult": ".types", + "DealsBioResponse": ".deals", + "DealsDebtLendersResponse": ".deals", + "DealsDebtLendersResponseLendersItem": ".deals", + "DealsInvestorsResponse": ".deals", + "DealsInvestorsResponseParticipantsItem": ".deals", + "DealsSearchResponse": ".deals", + "DealsSearchResponseResultsItem": ".deals", + "DealsServiceProvidersResponse": ".deals", + "DealsServiceProvidersResponseServiceProvidersItem": ".deals", + "DealsStockInfoResponse": ".deals", + "DealsValuationResponse": ".deals", + "DealsValuationResponseTerms": ".deals", "DocumentDeleteResponseV2": ".types", "DocumentItemV2": ".types", "DocumentListResponseV2": ".types", @@ -129,10 +333,39 @@ "FileStatusStatus": ".types", "FilesPage": ".types", "ForbiddenError": ".errors", + "FundamentalsLookupTableValuesResponse": ".sandbox_data", + "FundamentalsLookupTableValuesResponseValuesItem": ".sandbox_data", + "FundamentalsLookupTablesResponse": ".sandbox_data", + "FundamentalsLookupTablesResponseTablesItem": ".sandbox_data", + "FundamentalsSandboxResponse": ".sandbox_data", + "FundamentalsSandboxResponseCompaniesItem": ".sandbox_data", + "FundamentalsSandboxResponseInvestorsItem": ".sandbox_data", + "FundamentalsSandboxResponsePeopleItem": ".sandbox_data", + "FundsActiveInvestmentsResponse": ".funds", + "FundsActiveInvestmentsResponseInvestmentsItem": ".funds", + "FundsBioResponse": ".funds", + "FundsBioResponseData": ".funds", + "FundsSearchResponse": ".funds", + "FundsSearchResponseResultsItem": ".funds", + "FundsSearchResponseResultsItemLocation": ".funds", + "GeneralEntityAffiliatesResponse": ".general", + "GeneralEntityAffiliatesResponseAffiliatesItem": ".general", + "GeneralEntityLocationsResponse": ".general", + "GeneralEntityLocationsResponseLocationsItem": ".general", + "GeneralEntityNewsResponse": ".general", + "GeneralEntityNewsResponseNewsItem": ".general", + "GeneralEntityPeopleResponse": ".general", + "GeneralEntityPeopleResponsePeopleItem": ".general", + "GeneralSearchRequestEntityType": ".general", + "GeneralSearchResponse": ".general", + "GeneralSearchResponseResultsItem": ".general", + "GeneralSearchSharedResponse": ".general", + "GeneralSearchSharedResponseResultsItem": ".general", "HttpValidationError": ".types", "IndexAzureDirectoryRequestV2ProcessingType": ".indexing", "IndexAzureFileRequestV2ProcessingType": ".indexing", "IndexAzureRequestV2ProcessingType": ".indexing", + "IndexFileV2RequestProcessingType": ".indexing", "IndexGcsDirectoryRequestV2ProcessingType": ".indexing", "IndexGcsFileRequestV2ProcessingType": ".indexing", "IndexGcsRequestV2ProcessingType": ".indexing", @@ -148,17 +381,52 @@ "IndexS3FileRequestV2ProcessingType": ".indexing", "IndexS3RequestV2ProcessingType": ".indexing", "IndexUrlRequestV2ProcessingType": ".indexing", + "InvestorsActiveInvestmentsResponse": ".investors", + "InvestorsActiveInvestmentsResponsePortfolioItem": ".investors", + "InvestorsBioResponse": ".investors", + "InvestorsBioResponseHeadquarters": ".investors", + "InvestorsBioResponseInvestorType": ".investors", + "InvestorsBoardSeatsResponse": ".investors", + "InvestorsBoardSeatsResponseBoardSeatsItem": ".investors", + "InvestorsFundsLatestResponse": ".investors", + "InvestorsFundsLatestResponseFundsItem": ".investors", + "InvestorsFundsLatestResponseLatestFund": ".investors", + "InvestorsFundsResponse": ".investors", + "InvestorsFundsResponseFundsItem": ".investors", + "InvestorsPreferencesResponse": ".investors", + "InvestorsSearchResponse": ".investors", + "InvestorsSearchResponseResultsItem": ".investors", + "InvestorsServiceProvidersDealResponse": ".investors", + "InvestorsServiceProvidersResponse": ".investors", "JobBilling": ".types", "JobBillingProcessingType": ".types", "JobCancelResponseV2": ".types", "JobProgress": ".types", "JobProgressCurrentStage": ".types", "JobResult": ".types", + "JobRollbackResponseV2": ".types", "JobStatus": ".types", "JobStatusResponseV2": ".types", "JobStatusResponseV2JobType": ".types", + "LpsBioResponse": ".limited_partners", + "LpsBioResponseData": ".limited_partners", + "LpsSearchResponse": ".limited_partners", + "LpsSearchResponseResultsItem": ".limited_partners", + "LpsSearchResponseResultsItemLocation": ".limited_partners", "NotFoundError": ".errors", "NotImplementedError": ".errors", + "NotImplementedErrorBody": ".types", + "NotImplementedErrorBodyDetails": ".types", + "PatentsGetByIdResponse": ".patents", + "PatentsGetFileResponse": ".patents", + "PatentsSearchResponse": ".patents", + "PatentsSearchResponsePatentsItem": ".patents", + "PatentsSearchResponseResultsItem": ".patents", + "PeopleBioResponse": ".people", + "PeopleBioResponseCurrentCompany": ".people", + "PeopleBioResponseLocation": ".people", + "PeopleSearchResponse": ".people", + "PeopleSearchResponseResultsItem": ".people", "QueryResponseV2": ".types", "QueryStreamCompleteEvent": ".types", "QueryStreamErrorEvent": ".types", @@ -171,12 +439,28 @@ "QueryStreamTextEvent": ".types", "QueryStreamToolEndEvent": ".types", "QueryStreamToolStartEvent": ".types", - "RelevantDocumentV2": ".types", + "ScientificAskResponse": ".types", + "ScientificSource": ".types", "SearchResult": ".types", + "ServiceProvidersBioResponse": ".service_providers", + "ServiceProvidersCompaniesResponse": ".service_providers", + "ServiceProvidersCompaniesResponseCompaniesItem": ".service_providers", + "ServiceProvidersDealsResponse": ".service_providers", + "ServiceProvidersDealsResponseDealsItem": ".service_providers", + "ServiceProvidersFundsResponse": ".service_providers", + "ServiceProvidersFundsResponseFundsItem": ".service_providers", + "ServiceProvidersInvestorsResponse": ".service_providers", + "ServiceProvidersInvestorsResponseInvestorsItem": ".service_providers", + "ServiceProvidersSearchResponse": ".service_providers", + "ServiceProvidersSearchResponseResultsItem": ".service_providers", + "ServiceProvidersSearchResponseResultsItemLocation": ".service_providers", "ServiceUnavailableError": ".errors", "StandardResponseV2": ".types", "TokenBalance": ".types", "UnauthorizedError": ".errors", + "UnprocessableEntityError": ".errors", + "ValidateParsingScriptResponseV2": ".types", + "ValidateParsingScriptResponseV2ErrorType": ".types", "ValidationError": ".types", "ValidationErrorLocItem": ".types", "__version__": ".version", @@ -185,7 +469,6 @@ "credit_analysis": ".credit_analysis", "datasets": ".datasets", "deals": ".deals", - "fundamentals": ".fundamentals", "funds": ".funds", "general": ".general", "indexing": ".indexing", @@ -195,6 +478,7 @@ "patents": ".patents", "people": ".people", "query": ".query", + "sandbox_data": ".sandbox_data", "service_providers": ".service_providers", } @@ -234,10 +518,61 @@ def __dir__(): "CollectionItemV2", "CollectionListResponseV2", "CollectionResponseV2", + "CompaniesActiveInvestorsResponse", + "CompaniesActiveInvestorsResponseInvestorsItem", + "CompaniesBioResponse", + "CompaniesBioResponseHeadquarters", + "CompaniesBioResponseSocialProfiles", + "CompaniesDealsResponse", + "CompaniesDealsResponseDealsItem", + "CompaniesDebtFinancingRecentResponse", + "CompaniesDebtFinancingRecentResponseRound", + "CompaniesFinancialsRecentResponse", + "CompaniesFinancialsResponse", + "CompaniesFinancingRecentResponse", + "CompaniesFinancingRecentResponseRoundsItem", + "CompaniesFinancingRecentResponseWebSourcesItem", + "CompaniesFullResponse", + "CompaniesFullResponseAffiliatedEntitiesItem", + "CompaniesFullResponseFundingDetailsItem", + "CompaniesFullResponseHeadquarters", + "CompaniesFullResponseLocation", + "CompaniesFullResponseSocialProfiles", + "CompaniesSearchResponse", + "CompaniesSearchResponseResultsItem", + "CompaniesServiceProvidersDealResponse", + "CompaniesServiceProvidersDealResponseServiceProvidersItem", + "CompaniesServiceProvidersResponse", + "CompaniesServiceProvidersResponseServiceProvidersItem", + "CompaniesSimilarResponse", + "CompaniesSimilarResponseCompetitorsItem", "ConflictError", + "ConflictErrorBody", + "CreditAnalysisBdcSearchRequestSeniority", + "CreditAnalysisFundsSearchRequestStrategy", + "CreditAnalysisNewsBulkResponse", + "CreditAnalysisNewsBulkResponseResultsItem", + "CreditAnalysisNewsDetailResponse", + "CreditAnalysisNewsRecentResponse", + "CreditAnalysisNewsRecentResponseResultsItem", + "CreditAnalysisNewsSearchResponse", + "CreditAnalysisNewsSearchResponseResultsItem", + "CreditAnalysisSbaSearchRequestStatus", "DatasetArticleResponse", "DatasetSearchResponse", "DatasetSearchResult", + "DealsBioResponse", + "DealsDebtLendersResponse", + "DealsDebtLendersResponseLendersItem", + "DealsInvestorsResponse", + "DealsInvestorsResponseParticipantsItem", + "DealsSearchResponse", + "DealsSearchResponseResultsItem", + "DealsServiceProvidersResponse", + "DealsServiceProvidersResponseServiceProvidersItem", + "DealsStockInfoResponse", + "DealsValuationResponse", + "DealsValuationResponseTerms", "DocumentDeleteResponseV2", "DocumentItemV2", "DocumentListResponseV2", @@ -245,10 +580,39 @@ def __dir__(): "FileStatusStatus", "FilesPage", "ForbiddenError", + "FundamentalsLookupTableValuesResponse", + "FundamentalsLookupTableValuesResponseValuesItem", + "FundamentalsLookupTablesResponse", + "FundamentalsLookupTablesResponseTablesItem", + "FundamentalsSandboxResponse", + "FundamentalsSandboxResponseCompaniesItem", + "FundamentalsSandboxResponseInvestorsItem", + "FundamentalsSandboxResponsePeopleItem", + "FundsActiveInvestmentsResponse", + "FundsActiveInvestmentsResponseInvestmentsItem", + "FundsBioResponse", + "FundsBioResponseData", + "FundsSearchResponse", + "FundsSearchResponseResultsItem", + "FundsSearchResponseResultsItemLocation", + "GeneralEntityAffiliatesResponse", + "GeneralEntityAffiliatesResponseAffiliatesItem", + "GeneralEntityLocationsResponse", + "GeneralEntityLocationsResponseLocationsItem", + "GeneralEntityNewsResponse", + "GeneralEntityNewsResponseNewsItem", + "GeneralEntityPeopleResponse", + "GeneralEntityPeopleResponsePeopleItem", + "GeneralSearchRequestEntityType", + "GeneralSearchResponse", + "GeneralSearchResponseResultsItem", + "GeneralSearchSharedResponse", + "GeneralSearchSharedResponseResultsItem", "HttpValidationError", "IndexAzureDirectoryRequestV2ProcessingType", "IndexAzureFileRequestV2ProcessingType", "IndexAzureRequestV2ProcessingType", + "IndexFileV2RequestProcessingType", "IndexGcsDirectoryRequestV2ProcessingType", "IndexGcsFileRequestV2ProcessingType", "IndexGcsRequestV2ProcessingType", @@ -264,17 +628,52 @@ def __dir__(): "IndexS3FileRequestV2ProcessingType", "IndexS3RequestV2ProcessingType", "IndexUrlRequestV2ProcessingType", + "InvestorsActiveInvestmentsResponse", + "InvestorsActiveInvestmentsResponsePortfolioItem", + "InvestorsBioResponse", + "InvestorsBioResponseHeadquarters", + "InvestorsBioResponseInvestorType", + "InvestorsBoardSeatsResponse", + "InvestorsBoardSeatsResponseBoardSeatsItem", + "InvestorsFundsLatestResponse", + "InvestorsFundsLatestResponseFundsItem", + "InvestorsFundsLatestResponseLatestFund", + "InvestorsFundsResponse", + "InvestorsFundsResponseFundsItem", + "InvestorsPreferencesResponse", + "InvestorsSearchResponse", + "InvestorsSearchResponseResultsItem", + "InvestorsServiceProvidersDealResponse", + "InvestorsServiceProvidersResponse", "JobBilling", "JobBillingProcessingType", "JobCancelResponseV2", "JobProgress", "JobProgressCurrentStage", "JobResult", + "JobRollbackResponseV2", "JobStatus", "JobStatusResponseV2", "JobStatusResponseV2JobType", + "LpsBioResponse", + "LpsBioResponseData", + "LpsSearchResponse", + "LpsSearchResponseResultsItem", + "LpsSearchResponseResultsItemLocation", "NotFoundError", "NotImplementedError", + "NotImplementedErrorBody", + "NotImplementedErrorBodyDetails", + "PatentsGetByIdResponse", + "PatentsGetFileResponse", + "PatentsSearchResponse", + "PatentsSearchResponsePatentsItem", + "PatentsSearchResponseResultsItem", + "PeopleBioResponse", + "PeopleBioResponseCurrentCompany", + "PeopleBioResponseLocation", + "PeopleSearchResponse", + "PeopleSearchResponseResultsItem", "QueryResponseV2", "QueryStreamCompleteEvent", "QueryStreamErrorEvent", @@ -287,12 +686,28 @@ def __dir__(): "QueryStreamTextEvent", "QueryStreamToolEndEvent", "QueryStreamToolStartEvent", - "RelevantDocumentV2", + "ScientificAskResponse", + "ScientificSource", "SearchResult", + "ServiceProvidersBioResponse", + "ServiceProvidersCompaniesResponse", + "ServiceProvidersCompaniesResponseCompaniesItem", + "ServiceProvidersDealsResponse", + "ServiceProvidersDealsResponseDealsItem", + "ServiceProvidersFundsResponse", + "ServiceProvidersFundsResponseFundsItem", + "ServiceProvidersInvestorsResponse", + "ServiceProvidersInvestorsResponseInvestorsItem", + "ServiceProvidersSearchResponse", + "ServiceProvidersSearchResponseResultsItem", + "ServiceProvidersSearchResponseResultsItemLocation", "ServiceUnavailableError", "StandardResponseV2", "TokenBalance", "UnauthorizedError", + "UnprocessableEntityError", + "ValidateParsingScriptResponseV2", + "ValidateParsingScriptResponseV2ErrorType", "ValidationError", "ValidationErrorLocItem", "__version__", @@ -301,7 +716,6 @@ def __dir__(): "credit_analysis", "datasets", "deals", - "fundamentals", "funds", "general", "indexing", @@ -311,5 +725,6 @@ def __dir__(): "patents", "people", "query", + "sandbox_data", "service_providers", ] diff --git a/src/runcaptain/client.py b/src/runcaptain/client.py index 663ca3b..83e6169 100644 --- a/src/runcaptain/client.py +++ b/src/runcaptain/client.py @@ -17,7 +17,6 @@ from .credit_analysis.client import AsyncCreditAnalysisClient, CreditAnalysisClient from .datasets.client import AsyncDatasetsClient, DatasetsClient from .deals.client import AsyncDealsClient, DealsClient - from .fundamentals.client import AsyncFundamentalsClient, FundamentalsClient from .funds.client import AsyncFundsClient, FundsClient from .general.client import AsyncGeneralClient, GeneralClient from .indexing.client import AsyncIndexingClient, IndexingClient @@ -27,6 +26,7 @@ from .patents.client import AsyncPatentsClient, PatentsClient from .people.client import AsyncPeopleClient, PeopleClient from .query.client import AsyncQueryClient, QueryClient + from .sandbox_data.client import AsyncSandboxDataClient, SandboxDataClient from .service_providers.client import AsyncServiceProvidersClient, ServiceProvidersClient @@ -121,7 +121,7 @@ def __init__( self._service_providers: typing.Optional[ServiceProvidersClient] = None self._patents: typing.Optional[PatentsClient] = None self._credit_analysis: typing.Optional[CreditAnalysisClient] = None - self._fundamentals: typing.Optional[FundamentalsClient] = None + self._sandbox_data: typing.Optional[SandboxDataClient] = None @property def collections(self): @@ -244,12 +244,12 @@ def credit_analysis(self): return self._credit_analysis @property - def fundamentals(self): - if self._fundamentals is None: - from .fundamentals.client import FundamentalsClient # noqa: E402 + def sandbox_data(self): + if self._sandbox_data is None: + from .sandbox_data.client import SandboxDataClient # noqa: E402 - self._fundamentals = FundamentalsClient(client_wrapper=self._client_wrapper) - return self._fundamentals + self._sandbox_data = SandboxDataClient(client_wrapper=self._client_wrapper) + return self._sandbox_data class AsyncCaptain: @@ -343,7 +343,7 @@ def __init__( self._service_providers: typing.Optional[AsyncServiceProvidersClient] = None self._patents: typing.Optional[AsyncPatentsClient] = None self._credit_analysis: typing.Optional[AsyncCreditAnalysisClient] = None - self._fundamentals: typing.Optional[AsyncFundamentalsClient] = None + self._sandbox_data: typing.Optional[AsyncSandboxDataClient] = None @property def collections(self): @@ -466,12 +466,12 @@ def credit_analysis(self): return self._credit_analysis @property - def fundamentals(self): - if self._fundamentals is None: - from .fundamentals.client import AsyncFundamentalsClient # noqa: E402 + def sandbox_data(self): + if self._sandbox_data is None: + from .sandbox_data.client import AsyncSandboxDataClient # noqa: E402 - self._fundamentals = AsyncFundamentalsClient(client_wrapper=self._client_wrapper) - return self._fundamentals + self._sandbox_data = AsyncSandboxDataClient(client_wrapper=self._client_wrapper) + return self._sandbox_data def _get_base_url(*, base_url: typing.Optional[str] = None, environment: CaptainEnvironment) -> str: diff --git a/src/runcaptain/collections/client.py b/src/runcaptain/collections/client.py index eb76ec8..83498e6 100644 --- a/src/runcaptain/collections/client.py +++ b/src/runcaptain/collections/client.py @@ -33,7 +33,11 @@ def with_raw_response(self) -> RawCollectionsClient: return self._raw_client def list_collections_v2( - self, *, request_options: typing.Optional[RequestOptions] = None + self, + *, + limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> CollectionListResponseV2: """ List all collections for an organization. @@ -42,6 +46,12 @@ def list_collections_v2( Parameters ---------- + limit : typing.Optional[int] + Maximum number of collections to return + + offset : typing.Optional[int] + Pagination offset + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -60,7 +70,7 @@ def list_collections_v2( ) client.collections.list_collections_v2() """ - _response = self._raw_client.list_collections_v2(request_options=request_options) + _response = self._raw_client.list_collections_v2(limit=limit, offset=offset, request_options=request_options) return _response.data def create_collection_v2( @@ -76,6 +86,7 @@ def create_collection_v2( Parameters ---------- collection_name : str + Name of the collection to create description : typing.Optional[str] @@ -114,6 +125,7 @@ def delete_collection_v2( Parameters ---------- collection_name : str + Name of the collection to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -148,7 +160,7 @@ def change_collection_environment_v2( """ Move a collection from one environment to another (e.g., development to production) without reindexing. - All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same — only the environment label changes. + All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same - only the environment label changes. ## Use Cases - Promote a development collection to production after testing @@ -163,6 +175,7 @@ def change_collection_environment_v2( Parameters ---------- collection_name : str + Name of the collection to move new_environment : ChangeEnvironmentRequestV2NewEnvironment The target environment to move the collection to @@ -197,6 +210,7 @@ def list_documents_v2( self, collection_name: str, *, + limit: typing.Optional[int] = None, offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> DocumentListResponseV2: @@ -206,6 +220,10 @@ def list_documents_v2( Parameters ---------- collection_name : str + Name of the collection + + limit : typing.Optional[int] + Maximum number of documents to return offset : typing.Optional[int] Pagination offset @@ -230,7 +248,9 @@ def list_documents_v2( collection_name="my_documents", ) """ - _response = self._raw_client.list_documents_v2(collection_name, offset=offset, request_options=request_options) + _response = self._raw_client.list_documents_v2( + collection_name, limit=limit, offset=offset, request_options=request_options + ) return _response.data def wipe_collection_documents_v2( @@ -242,6 +262,7 @@ def wipe_collection_documents_v2( Parameters ---------- collection_name : str + Name of the collection to wipe request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -260,7 +281,7 @@ def wipe_collection_documents_v2( key="YOUR_KEY", ) client.collections.wipe_collection_documents_v2( - collection_name="my_documents", + collection_name="customer_profiles", ) """ _response = self._raw_client.wipe_collection_documents_v2(collection_name, request_options=request_options) @@ -275,8 +296,10 @@ def delete_document_v2( Parameters ---------- collection_name : str + Name of the collection document_id : str + ID of the document to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -295,8 +318,8 @@ def delete_document_v2( key="YOUR_KEY", ) client.collections.delete_document_v2( - collection_name="my_documents", - document_id="doc_abc123", + collection_name="customer_feedback", + document_id="a1b2c3d4e5f678901234567890abcdef", ) """ _response = self._raw_client.delete_document_v2(collection_name, document_id, request_options=request_options) @@ -319,7 +342,11 @@ def with_raw_response(self) -> AsyncRawCollectionsClient: return self._raw_client async def list_collections_v2( - self, *, request_options: typing.Optional[RequestOptions] = None + self, + *, + limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> CollectionListResponseV2: """ List all collections for an organization. @@ -328,6 +355,12 @@ async def list_collections_v2( Parameters ---------- + limit : typing.Optional[int] + Maximum number of collections to return + + offset : typing.Optional[int] + Pagination offset + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -354,7 +387,9 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.list_collections_v2(request_options=request_options) + _response = await self._raw_client.list_collections_v2( + limit=limit, offset=offset, request_options=request_options + ) return _response.data async def create_collection_v2( @@ -370,6 +405,7 @@ async def create_collection_v2( Parameters ---------- collection_name : str + Name of the collection to create description : typing.Optional[str] @@ -416,6 +452,7 @@ async def delete_collection_v2( Parameters ---------- collection_name : str + Name of the collection to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -458,7 +495,7 @@ async def change_collection_environment_v2( """ Move a collection from one environment to another (e.g., development to production) without reindexing. - All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same — only the environment label changes. + All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same - only the environment label changes. ## Use Cases - Promote a development collection to production after testing @@ -473,6 +510,7 @@ async def change_collection_environment_v2( Parameters ---------- collection_name : str + Name of the collection to move new_environment : ChangeEnvironmentRequestV2NewEnvironment The target environment to move the collection to @@ -515,6 +553,7 @@ async def list_documents_v2( self, collection_name: str, *, + limit: typing.Optional[int] = None, offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> DocumentListResponseV2: @@ -524,6 +563,10 @@ async def list_documents_v2( Parameters ---------- collection_name : str + Name of the collection + + limit : typing.Optional[int] + Maximum number of documents to return offset : typing.Optional[int] Pagination offset @@ -557,7 +600,7 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.list_documents_v2( - collection_name, offset=offset, request_options=request_options + collection_name, limit=limit, offset=offset, request_options=request_options ) return _response.data @@ -570,6 +613,7 @@ async def wipe_collection_documents_v2( Parameters ---------- collection_name : str + Name of the collection to wipe request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -593,7 +637,7 @@ async def wipe_collection_documents_v2( async def main() -> None: await client.collections.wipe_collection_documents_v2( - collection_name="my_documents", + collection_name="customer_profiles", ) @@ -613,8 +657,10 @@ async def delete_document_v2( Parameters ---------- collection_name : str + Name of the collection document_id : str + ID of the document to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -638,8 +684,8 @@ async def delete_document_v2( async def main() -> None: await client.collections.delete_document_v2( - collection_name="my_documents", - document_id="doc_abc123", + collection_name="customer_feedback", + document_id="a1b2c3d4e5f678901234567890abcdef", ) diff --git a/src/runcaptain/collections/raw_client.py b/src/runcaptain/collections/raw_client.py index fedfa71..efc7dec 100644 --- a/src/runcaptain/collections/raw_client.py +++ b/src/runcaptain/collections/raw_client.py @@ -7,6 +7,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.bad_request_error import BadRequestError @@ -19,6 +20,7 @@ from ..types.document_list_response_v2 import DocumentListResponseV2 from ..types.standard_response_v2 import StandardResponseV2 from .types.change_environment_request_v2new_environment import ChangeEnvironmentRequestV2NewEnvironment +from pydantic import ValidationError # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -29,7 +31,11 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def list_collections_v2( - self, *, request_options: typing.Optional[RequestOptions] = None + self, + *, + limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[CollectionListResponseV2]: """ List all collections for an organization. @@ -38,6 +44,12 @@ def list_collections_v2( Parameters ---------- + limit : typing.Optional[int] + Maximum number of collections to return + + offset : typing.Optional[int] + Pagination offset + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -49,6 +61,10 @@ def list_collections_v2( _response = self._client_wrapper.httpx_client.request( "v2/collections", method="GET", + params={ + "limit": limit, + "offset": offset, + }, request_options=request_options, ) try: @@ -64,6 +80,10 @@ def list_collections_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def create_collection_v2( @@ -79,6 +99,7 @@ def create_collection_v2( Parameters ---------- collection_name : str + Name of the collection to create description : typing.Optional[str] @@ -115,6 +136,10 @@ def create_collection_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def delete_collection_v2( @@ -126,6 +151,7 @@ def delete_collection_v2( Parameters ---------- collection_name : str + Name of the collection to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -164,6 +190,10 @@ def delete_collection_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def change_collection_environment_v2( @@ -176,7 +206,7 @@ def change_collection_environment_v2( """ Move a collection from one environment to another (e.g., development to production) without reindexing. - All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same — only the environment label changes. + All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same - only the environment label changes. ## Use Cases - Promote a development collection to production after testing @@ -191,6 +221,7 @@ def change_collection_environment_v2( Parameters ---------- collection_name : str + Name of the collection to move new_environment : ChangeEnvironmentRequestV2NewEnvironment The target environment to move the collection to @@ -261,12 +292,17 @@ def change_collection_environment_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def list_documents_v2( self, collection_name: str, *, + limit: typing.Optional[int] = None, offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[DocumentListResponseV2]: @@ -276,6 +312,10 @@ def list_documents_v2( Parameters ---------- collection_name : str + Name of the collection + + limit : typing.Optional[int] + Maximum number of documents to return offset : typing.Optional[int] Pagination offset @@ -292,6 +332,7 @@ def list_documents_v2( f"v2/collections/{jsonable_encoder(collection_name)}/documents", method="GET", params={ + "limit": limit, "offset": offset, }, request_options=request_options, @@ -309,6 +350,10 @@ def list_documents_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def wipe_collection_documents_v2( @@ -320,6 +365,7 @@ def wipe_collection_documents_v2( Parameters ---------- collection_name : str + Name of the collection to wipe request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -347,6 +393,10 @@ def wipe_collection_documents_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def delete_document_v2( @@ -358,8 +408,10 @@ def delete_document_v2( Parameters ---------- collection_name : str + Name of the collection document_id : str + ID of the document to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -398,6 +450,10 @@ def delete_document_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -406,7 +462,11 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def list_collections_v2( - self, *, request_options: typing.Optional[RequestOptions] = None + self, + *, + limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[CollectionListResponseV2]: """ List all collections for an organization. @@ -415,6 +475,12 @@ async def list_collections_v2( Parameters ---------- + limit : typing.Optional[int] + Maximum number of collections to return + + offset : typing.Optional[int] + Pagination offset + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -426,6 +492,10 @@ async def list_collections_v2( _response = await self._client_wrapper.httpx_client.request( "v2/collections", method="GET", + params={ + "limit": limit, + "offset": offset, + }, request_options=request_options, ) try: @@ -441,6 +511,10 @@ async def list_collections_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def create_collection_v2( @@ -456,6 +530,7 @@ async def create_collection_v2( Parameters ---------- collection_name : str + Name of the collection to create description : typing.Optional[str] @@ -492,6 +567,10 @@ async def create_collection_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def delete_collection_v2( @@ -503,6 +582,7 @@ async def delete_collection_v2( Parameters ---------- collection_name : str + Name of the collection to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -541,6 +621,10 @@ async def delete_collection_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def change_collection_environment_v2( @@ -553,7 +637,7 @@ async def change_collection_environment_v2( """ Move a collection from one environment to another (e.g., development to production) without reindexing. - All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same — only the environment label changes. + All files, indexed data, and vector embeddings are preserved. The collection's internal ID stays the same - only the environment label changes. ## Use Cases - Promote a development collection to production after testing @@ -568,6 +652,7 @@ async def change_collection_environment_v2( Parameters ---------- collection_name : str + Name of the collection to move new_environment : ChangeEnvironmentRequestV2NewEnvironment The target environment to move the collection to @@ -638,12 +723,17 @@ async def change_collection_environment_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def list_documents_v2( self, collection_name: str, *, + limit: typing.Optional[int] = None, offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[DocumentListResponseV2]: @@ -653,6 +743,10 @@ async def list_documents_v2( Parameters ---------- collection_name : str + Name of the collection + + limit : typing.Optional[int] + Maximum number of documents to return offset : typing.Optional[int] Pagination offset @@ -669,6 +763,7 @@ async def list_documents_v2( f"v2/collections/{jsonable_encoder(collection_name)}/documents", method="GET", params={ + "limit": limit, "offset": offset, }, request_options=request_options, @@ -686,6 +781,10 @@ async def list_documents_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def wipe_collection_documents_v2( @@ -697,6 +796,7 @@ async def wipe_collection_documents_v2( Parameters ---------- collection_name : str + Name of the collection to wipe request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -724,6 +824,10 @@ async def wipe_collection_documents_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def delete_document_v2( @@ -735,8 +839,10 @@ async def delete_document_v2( Parameters ---------- collection_name : str + Name of the collection document_id : str + ID of the document to delete request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -775,4 +881,8 @@ async def delete_document_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/companies/__init__.py b/src/runcaptain/companies/__init__.py index 5cde020..b637ba5 100644 --- a/src/runcaptain/companies/__init__.py +++ b/src/runcaptain/companies/__init__.py @@ -2,3 +2,120 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CompaniesActiveInvestorsResponse, + CompaniesActiveInvestorsResponseInvestorsItem, + CompaniesBioResponse, + CompaniesBioResponseHeadquarters, + CompaniesBioResponseSocialProfiles, + CompaniesDealsResponse, + CompaniesDealsResponseDealsItem, + CompaniesDebtFinancingRecentResponse, + CompaniesDebtFinancingRecentResponseRound, + CompaniesFinancialsRecentResponse, + CompaniesFinancialsResponse, + CompaniesFinancingRecentResponse, + CompaniesFinancingRecentResponseRoundsItem, + CompaniesFinancingRecentResponseWebSourcesItem, + CompaniesFullResponse, + CompaniesFullResponseAffiliatedEntitiesItem, + CompaniesFullResponseFundingDetailsItem, + CompaniesFullResponseHeadquarters, + CompaniesFullResponseLocation, + CompaniesFullResponseSocialProfiles, + CompaniesSearchResponse, + CompaniesSearchResponseResultsItem, + CompaniesServiceProvidersDealResponse, + CompaniesServiceProvidersDealResponseServiceProvidersItem, + CompaniesServiceProvidersResponse, + CompaniesServiceProvidersResponseServiceProvidersItem, + CompaniesSimilarResponse, + CompaniesSimilarResponseCompetitorsItem, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CompaniesActiveInvestorsResponse": ".types", + "CompaniesActiveInvestorsResponseInvestorsItem": ".types", + "CompaniesBioResponse": ".types", + "CompaniesBioResponseHeadquarters": ".types", + "CompaniesBioResponseSocialProfiles": ".types", + "CompaniesDealsResponse": ".types", + "CompaniesDealsResponseDealsItem": ".types", + "CompaniesDebtFinancingRecentResponse": ".types", + "CompaniesDebtFinancingRecentResponseRound": ".types", + "CompaniesFinancialsRecentResponse": ".types", + "CompaniesFinancialsResponse": ".types", + "CompaniesFinancingRecentResponse": ".types", + "CompaniesFinancingRecentResponseRoundsItem": ".types", + "CompaniesFinancingRecentResponseWebSourcesItem": ".types", + "CompaniesFullResponse": ".types", + "CompaniesFullResponseAffiliatedEntitiesItem": ".types", + "CompaniesFullResponseFundingDetailsItem": ".types", + "CompaniesFullResponseHeadquarters": ".types", + "CompaniesFullResponseLocation": ".types", + "CompaniesFullResponseSocialProfiles": ".types", + "CompaniesSearchResponse": ".types", + "CompaniesSearchResponseResultsItem": ".types", + "CompaniesServiceProvidersDealResponse": ".types", + "CompaniesServiceProvidersDealResponseServiceProvidersItem": ".types", + "CompaniesServiceProvidersResponse": ".types", + "CompaniesServiceProvidersResponseServiceProvidersItem": ".types", + "CompaniesSimilarResponse": ".types", + "CompaniesSimilarResponseCompetitorsItem": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CompaniesActiveInvestorsResponse", + "CompaniesActiveInvestorsResponseInvestorsItem", + "CompaniesBioResponse", + "CompaniesBioResponseHeadquarters", + "CompaniesBioResponseSocialProfiles", + "CompaniesDealsResponse", + "CompaniesDealsResponseDealsItem", + "CompaniesDebtFinancingRecentResponse", + "CompaniesDebtFinancingRecentResponseRound", + "CompaniesFinancialsRecentResponse", + "CompaniesFinancialsResponse", + "CompaniesFinancingRecentResponse", + "CompaniesFinancingRecentResponseRoundsItem", + "CompaniesFinancingRecentResponseWebSourcesItem", + "CompaniesFullResponse", + "CompaniesFullResponseAffiliatedEntitiesItem", + "CompaniesFullResponseFundingDetailsItem", + "CompaniesFullResponseHeadquarters", + "CompaniesFullResponseLocation", + "CompaniesFullResponseSocialProfiles", + "CompaniesSearchResponse", + "CompaniesSearchResponseResultsItem", + "CompaniesServiceProvidersDealResponse", + "CompaniesServiceProvidersDealResponseServiceProvidersItem", + "CompaniesServiceProvidersResponse", + "CompaniesServiceProvidersResponseServiceProvidersItem", + "CompaniesSimilarResponse", + "CompaniesSimilarResponseCompetitorsItem", +] diff --git a/src/runcaptain/companies/client.py b/src/runcaptain/companies/client.py index 208ea09..abd9aee 100644 --- a/src/runcaptain/companies/client.py +++ b/src/runcaptain/companies/client.py @@ -5,6 +5,18 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawCompaniesClient, RawCompaniesClient +from .types.companies_active_investors_response import CompaniesActiveInvestorsResponse +from .types.companies_bio_response import CompaniesBioResponse +from .types.companies_deals_response import CompaniesDealsResponse +from .types.companies_debt_financing_recent_response import CompaniesDebtFinancingRecentResponse +from .types.companies_financials_recent_response import CompaniesFinancialsRecentResponse +from .types.companies_financials_response import CompaniesFinancialsResponse +from .types.companies_financing_recent_response import CompaniesFinancingRecentResponse +from .types.companies_full_response import CompaniesFullResponse +from .types.companies_search_response import CompaniesSearchResponse +from .types.companies_service_providers_deal_response import CompaniesServiceProvidersDealResponse +from .types.companies_service_providers_response import CompaniesServiceProvidersResponse +from .types.companies_similar_response import CompaniesSimilarResponse class CompaniesClient: @@ -22,18 +34,39 @@ def with_raw_response(self) -> RawCompaniesClient: """ return self._raw_client - def search(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def search( + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> CompaniesSearchResponse: """ - Search for companies by name, industry, or location. Returns matching company profiles with employee count, industry classification, founding date, and headquarters. Use this to find company entity IDs for detailed lookups. + Search for companies by name or natural language description. + + ## Search Modes + + - **Direct lookup**: Short queries like `Stripe` or `OpenAI` resolve to a single company match + - **Natural language search**: Longer queries like `AI startups in San Francisco raising Series B` return up to 5 matching companies with surface-level data + + The endpoint auto-detects which mode to use based on the query. + + ## Response Fields + + Each result includes: name, website, description, employee_count, industry, location, founded, size, total_funding_raised, latest_funding_stage, tags, and linkedin_url. + + Use the entity_id or company identifier from results to call detail endpoints (bio, financing, investors, full). Parameters ---------- + q : str + Company name, domain, or natural language query (e.g. 'AI startups in San Francisco raising Series B') + + limit : typing.Optional[int] + Maximum results (default 5) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesSearchResponse Successful response Examples @@ -44,26 +77,36 @@ def search(self, *, request_options: typing.Optional[RequestOptions] = None) -> organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.companies.search() + client.companies.search( + q="OpenAI", + limit=10, + ) """ - _response = self._raw_client.search(request_options=request_options) + _response = self._raw_client.search(q=q, limit=limit, request_options=request_options) return _response.data - def bio(self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Any: + def full( + self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> CompaniesFullResponse: """ - Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. + Get the complete company record with ALL available data fields. + + Returns everything: base profile, funding details with amounts and investors, employee analytics and growth rates, executive changes, office locations, job postings, industry classifications, subsidiaries, and more. + + Use this after identifying a company via search to get the full picture. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Any - Company bio retrieved successfully + CompaniesFullResponse + Complete company record Examples -------- @@ -73,30 +116,29 @@ def bio(self, company_id: str, *, request_options: typing.Optional[RequestOption organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.companies.bio( - company_id="019cb8ac-adee-749e-a75e-a1c236f20f72", + client.companies.full( + company_id="anthropic.com", ) """ - _response = self._raw_client.bio(company_id, request_options=request_options) + _response = self._raw_client.full(company_id, request_options=request_options) return _response.data - def industries( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def bio(self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> CompaniesBioResponse: """ - Get industry classifications including NAICS codes, SIC codes, and industry descriptions. Useful for filtering companies by vertical or sector. + Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + CompaniesBioResponse + Company bio retrieved successfully Examples -------- @@ -106,29 +148,37 @@ def industries( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.companies.industries( - company_id="openai.com", + client.companies.bio( + company_id="019cb8ac-adee-749e-a75e-a1c236f20f72", ) """ - _response = self._raw_client.industries(company_id, request_options=request_options) + _response = self._raw_client.bio(company_id, request_options=request_options) return _response.data def financials( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + company_id: str, + *, + fiscal_year: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CompaniesFinancialsResponse: """ Get financial statements for public companies including revenue, net income, assets, and liabilities. Returns most recent fiscal year data or specific year if requested. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + + fiscal_year : typing.Optional[int] + Fiscal year (default: most recent) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesFinancialsResponse Successful response Examples @@ -143,25 +193,26 @@ def financials( company_id="ody_co_sample_msft", ) """ - _response = self._raw_client.financials(company_id, request_options=request_options) + _response = self._raw_client.financials(company_id, fiscal_year=fiscal_year, request_options=request_options) return _response.data def financials_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesFinancialsRecentResponse: """ Get most recent financial statements for public companies. Convenience endpoint that automatically returns the latest available fiscal year data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesFinancialsRecentResponse Successful response Examples @@ -181,20 +232,21 @@ def financials_recent( def financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesFinancingRecentResponse: """ Get most recent funding rounds including round type, amount raised, investors, and valuation. Returns detailed information about the latest equity financing event. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesFinancingRecentResponse Successful response Examples @@ -214,20 +266,21 @@ def financing_recent( def active_investors( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Any: + ) -> CompaniesActiveInvestorsResponse: """ Get current investors in the company from recent funding rounds. Returns investor names, types, and contact information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Any + CompaniesActiveInvestorsResponse Active investors retrieved successfully Examples @@ -245,53 +298,23 @@ def active_investors( _response = self._raw_client.active_investors(company_id, request_options=request_options) return _response.data - def all_investors( + def deals( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete investor history including historical investors from all funding rounds. Returns comprehensive list of all investors who have participated in company financing. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.companies.all_investors( - company_id="openai.com", - ) - """ - _response = self._raw_client.all_investors(company_id, request_options=request_options) - return _response.data - - def deals(self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Any: + ) -> CompaniesDealsResponse: """ Get all deals for a company including funding rounds and acquisitions. Each deal includes the deal type, round name, investors involved, amount raised, and status. Funding rounds are sourced from investor data, while acquisitions are sourced from M&A records. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Any + CompaniesDealsResponse Company deals retrieved successfully Examples @@ -311,20 +334,21 @@ def deals(self, company_id: str, *, request_options: typing.Optional[RequestOpti def service_providers( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesServiceProvidersResponse: """ Get service providers working with the company including legal counsel, accounting firms, and consultants. Returns firm names, service types, and engagement information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesServiceProvidersResponse Successful response Examples @@ -344,20 +368,21 @@ def service_providers( def service_providers_deal( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesServiceProvidersDealResponse: """ Get service providers involved in specific financing deals including investment bankers, legal advisors, and financial consultants. Returns provider details specific to fundraising transactions. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesServiceProvidersDealResponse Successful response Examples @@ -377,20 +402,21 @@ def service_providers_deal( def debt_financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesDebtFinancingRecentResponse: """ Get most recent debt financing rounds including venture debt, credit lines, and loans. Returns lender information, amounts, and terms. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesDebtFinancingRecentResponse Successful response Examples @@ -409,54 +435,29 @@ def debt_financing_recent( return _response.data def similar( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + company_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CompaniesSimilarResponse: """ Get companies similar to this one based on industry, size, and business model. Returns competitor and peer company information with similarity scores. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.companies.similar( - company_id="openai.com", - ) - """ - _response = self._raw_client.similar(company_id, request_options=request_options) - return _response.data - - def social_analytics( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get social media metrics including LinkedIn followers, Twitter engagement, and Facebook presence. Useful for measuring company online visibility and growth. - - Parameters - ---------- - company_id : str + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesSimilarResponse Successful response Examples @@ -467,30 +468,28 @@ def social_analytics( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.companies.social_analytics( + client.companies.similar( company_id="openai.com", ) """ - _response = self._raw_client.social_analytics(company_id, request_options=request_options) + _response = self._raw_client.similar(company_id, limit=limit, request_options=request_options) return _response.data - def vc_exit_predictions( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def vc_exit_predictions(self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get predicted likelihood and timeline for company exit events (IPO or acquisition). Will return ML-based exit probability scores and estimated exit valuation ranges when implemented. + **Coming Soon** - Get predicted likelihood and timeline for company exit events (IPO or acquisition). Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -507,39 +506,6 @@ def vc_exit_predictions( _response = self._raw_client.vc_exit_predictions(company_id, request_options=request_options) return _response.data - def updates( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to company profile data. Returns history of changes to company information with timestamps and modified fields. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.companies.updates( - company_id="openai.com", - ) - """ - _response = self._raw_client.updates(company_id, request_options=request_options) - return _response.data - class AsyncCompaniesClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -556,18 +522,39 @@ def with_raw_response(self) -> AsyncRawCompaniesClient: """ return self._raw_client - async def search(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + async def search( + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> CompaniesSearchResponse: """ - Search for companies by name, industry, or location. Returns matching company profiles with employee count, industry classification, founding date, and headquarters. Use this to find company entity IDs for detailed lookups. + Search for companies by name or natural language description. + + ## Search Modes + + - **Direct lookup**: Short queries like `Stripe` or `OpenAI` resolve to a single company match + - **Natural language search**: Longer queries like `AI startups in San Francisco raising Series B` return up to 5 matching companies with surface-level data + + The endpoint auto-detects which mode to use based on the query. + + ## Response Fields + + Each result includes: name, website, description, employee_count, industry, location, founded, size, total_funding_raised, latest_funding_stage, tags, and linkedin_url. + + Use the entity_id or company identifier from results to call detail endpoints (bio, financing, investors, full). Parameters ---------- + q : str + Company name, domain, or natural language query (e.g. 'AI startups in San Francisco raising Series B') + + limit : typing.Optional[int] + Maximum results (default 5) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesSearchResponse Successful response Examples @@ -583,29 +570,39 @@ async def search(self, *, request_options: typing.Optional[RequestOptions] = Non async def main() -> None: - await client.companies.search() + await client.companies.search( + q="OpenAI", + limit=10, + ) asyncio.run(main()) """ - _response = await self._raw_client.search(request_options=request_options) + _response = await self._raw_client.search(q=q, limit=limit, request_options=request_options) return _response.data - async def bio(self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Any: + async def full( + self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> CompaniesFullResponse: """ - Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. + Get the complete company record with ALL available data fields. + + Returns everything: base profile, funding details with amounts and investors, employee analytics and growth rates, executive changes, office locations, job postings, industry classifications, subsidiaries, and more. + + Use this after identifying a company via search to get the full picture. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Any - Company bio retrieved successfully + CompaniesFullResponse + Complete company record Examples -------- @@ -620,33 +617,34 @@ async def bio(self, company_id: str, *, request_options: typing.Optional[Request async def main() -> None: - await client.companies.bio( - company_id="019cb8ac-adee-749e-a75e-a1c236f20f72", + await client.companies.full( + company_id="anthropic.com", ) asyncio.run(main()) """ - _response = await self._raw_client.bio(company_id, request_options=request_options) + _response = await self._raw_client.full(company_id, request_options=request_options) return _response.data - async def industries( + async def bio( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesBioResponse: """ - Get industry classifications including NAICS codes, SIC codes, and industry descriptions. Useful for filtering companies by vertical or sector. + Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + CompaniesBioResponse + Company bio retrieved successfully Examples -------- @@ -661,32 +659,40 @@ async def industries( async def main() -> None: - await client.companies.industries( - company_id="openai.com", + await client.companies.bio( + company_id="019cb8ac-adee-749e-a75e-a1c236f20f72", ) asyncio.run(main()) """ - _response = await self._raw_client.industries(company_id, request_options=request_options) + _response = await self._raw_client.bio(company_id, request_options=request_options) return _response.data async def financials( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + company_id: str, + *, + fiscal_year: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CompaniesFinancialsResponse: """ Get financial statements for public companies including revenue, net income, assets, and liabilities. Returns most recent fiscal year data or specific year if requested. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + + fiscal_year : typing.Optional[int] + Fiscal year (default: most recent) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesFinancialsResponse Successful response Examples @@ -709,25 +715,28 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.financials(company_id, request_options=request_options) + _response = await self._raw_client.financials( + company_id, fiscal_year=fiscal_year, request_options=request_options + ) return _response.data async def financials_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesFinancialsRecentResponse: """ Get most recent financial statements for public companies. Convenience endpoint that automatically returns the latest available fiscal year data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesFinancialsRecentResponse Successful response Examples @@ -755,20 +764,21 @@ async def main() -> None: async def financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesFinancingRecentResponse: """ Get most recent funding rounds including round type, amount raised, investors, and valuation. Returns detailed information about the latest equity financing event. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesFinancingRecentResponse Successful response Examples @@ -796,20 +806,21 @@ async def main() -> None: async def active_investors( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Any: + ) -> CompaniesActiveInvestorsResponse: """ Get current investors in the company from recent funding rounds. Returns investor names, types, and contact information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Any + CompaniesActiveInvestorsResponse Active investors retrieved successfully Examples @@ -835,61 +846,23 @@ async def main() -> None: _response = await self._raw_client.active_investors(company_id, request_options=request_options) return _response.data - async def all_investors( + async def deals( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete investor history including historical investors from all funding rounds. Returns comprehensive list of all investors who have participated in company financing. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.companies.all_investors( - company_id="openai.com", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.all_investors(company_id, request_options=request_options) - return _response.data - - async def deals(self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Any: + ) -> CompaniesDealsResponse: """ Get all deals for a company including funding rounds and acquisitions. Each deal includes the deal type, round name, investors involved, amount raised, and status. Funding rounds are sourced from investor data, while acquisitions are sourced from M&A records. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Any + CompaniesDealsResponse Company deals retrieved successfully Examples @@ -917,20 +890,21 @@ async def main() -> None: async def service_providers( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesServiceProvidersResponse: """ Get service providers working with the company including legal counsel, accounting firms, and consultants. Returns firm names, service types, and engagement information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesServiceProvidersResponse Successful response Examples @@ -958,20 +932,21 @@ async def main() -> None: async def service_providers_deal( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesServiceProvidersDealResponse: """ Get service providers involved in specific financing deals including investment bankers, legal advisors, and financial consultants. Returns provider details specific to fundraising transactions. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesServiceProvidersDealResponse Successful response Examples @@ -999,20 +974,21 @@ async def main() -> None: async def debt_financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CompaniesDebtFinancingRecentResponse: """ Get most recent debt financing rounds including venture debt, credit lines, and loans. Returns lender information, amounts, and terms. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesDebtFinancingRecentResponse Successful response Examples @@ -1039,62 +1015,29 @@ async def main() -> None: return _response.data async def similar( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + company_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CompaniesSimilarResponse: """ Get companies similar to this one based on industry, size, and business model. Returns competitor and peer company information with similarity scores. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.companies.similar( - company_id="openai.com", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.similar(company_id, request_options=request_options) - return _response.data - - async def social_analytics( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get social media metrics including LinkedIn followers, Twitter engagement, and Facebook presence. Useful for measuring company online visibility and growth. - - Parameters - ---------- - company_id : str + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CompaniesSimilarResponse Successful response Examples @@ -1110,33 +1053,33 @@ async def social_analytics( async def main() -> None: - await client.companies.social_analytics( + await client.companies.similar( company_id="openai.com", ) asyncio.run(main()) """ - _response = await self._raw_client.social_analytics(company_id, request_options=request_options) + _response = await self._raw_client.similar(company_id, limit=limit, request_options=request_options) return _response.data async def vc_exit_predictions( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Coming Soon: Get predicted likelihood and timeline for company exit events (IPO or acquisition). Will return ML-based exit probability scores and estimated exit valuation ranges when implemented. + **Coming Soon** - Get predicted likelihood and timeline for company exit events (IPO or acquisition). Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -1160,44 +1103,3 @@ async def main() -> None: """ _response = await self._raw_client.vc_exit_predictions(company_id, request_options=request_options) return _response.data - - async def updates( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to company profile data. Returns history of changes to company information with timestamps and modified fields. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.companies.updates( - company_id="openai.com", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.updates(company_id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/companies/raw_client.py b/src/runcaptain/companies/raw_client.py index a549f15..e2db9ca 100644 --- a/src/runcaptain/companies/raw_client.py +++ b/src/runcaptain/companies/raw_client.py @@ -7,11 +7,25 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.companies_active_investors_response import CompaniesActiveInvestorsResponse +from .types.companies_bio_response import CompaniesBioResponse +from .types.companies_deals_response import CompaniesDealsResponse +from .types.companies_debt_financing_recent_response import CompaniesDebtFinancingRecentResponse +from .types.companies_financials_recent_response import CompaniesFinancialsRecentResponse +from .types.companies_financials_response import CompaniesFinancialsResponse +from .types.companies_financing_recent_response import CompaniesFinancingRecentResponse +from .types.companies_full_response import CompaniesFullResponse +from .types.companies_search_response import CompaniesSearchResponse +from .types.companies_service_providers_deal_response import CompaniesServiceProvidersDealResponse +from .types.companies_service_providers_response import CompaniesServiceProvidersResponse +from .types.companies_similar_response import CompaniesSimilarResponse +from pydantic import ValidationError class RawCompaniesClient: @@ -19,32 +33,55 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def search( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CompaniesSearchResponse]: """ - Search for companies by name, industry, or location. Returns matching company profiles with employee count, industry classification, founding date, and headquarters. Use this to find company entity IDs for detailed lookups. + Search for companies by name or natural language description. + + ## Search Modes + + - **Direct lookup**: Short queries like `Stripe` or `OpenAI` resolve to a single company match + - **Natural language search**: Longer queries like `AI startups in San Francisco raising Series B` return up to 5 matching companies with surface-level data + + The endpoint auto-detects which mode to use based on the query. + + ## Response Fields + + Each result includes: name, website, description, employee_count, industry, location, founded, size, total_funding_raised, latest_funding_stage, tags, and linkedin_url. + + Use the entity_id or company identifier from results to call detail endpoints (bio, financing, investors, full). Parameters ---------- + q : str + Company name, domain, or natural language query (e.g. 'AI startups in San Francisco raising Series B') + + limit : typing.Optional[int] + Maximum results (default 5) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/companies/search", method="GET", + params={ + "q": q, + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -63,54 +100,50 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def bio( + def full( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Any]: + ) -> HttpResponse[CompaniesFullResponse]: """ - Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. + Get the complete company record with ALL available data fields. + + Returns everything: base profile, funding details with amounts and investors, employee analytics and growth rates, executive changes, office locations, job postings, industry classifications, subsidiaries, and more. + + Use this after identifying a company via search to get the full picture. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Any] - Company bio retrieved successfully + HttpResponse[CompaniesFullResponse] + Complete company record """ _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/bio", + f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/full", method="GET", request_options=request_options, ) try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Any, + CompaniesFullResponse, parse_obj_as( - type_=typing.Any, # type: ignore + type_=CompaniesFullResponse, # type: ignore object_=_response.json(), ), ) return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) if _response.status_code == 404: raise NotFoundError( headers=dict(_response.headers), @@ -125,37 +158,42 @@ def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def industries( + def bio( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CompaniesBioResponse]: """ - Get industry classifications including NAICS codes, SIC codes, and industry descriptions. Useful for filtering companies by vertical or sector. + Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[CompaniesBioResponse] + Company bio retrieved successfully """ _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/industries", + f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/bio", method="GET", request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesBioResponse, # type: ignore object_=_response.json(), ), ) @@ -185,37 +223,52 @@ def industries( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def financials( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + company_id: str, + *, + fiscal_year: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CompaniesFinancialsResponse]: """ Get financial statements for public companies including revenue, net income, assets, and liabilities. Returns most recent fiscal year data or specific year if requested. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + + fiscal_year : typing.Optional[int] + Fiscal year (default: most recent) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesFinancialsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/financials", method="GET", + params={ + "fiscal_year": fiscal_year, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesFinancialsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesFinancialsResponse, # type: ignore object_=_response.json(), ), ) @@ -245,24 +298,29 @@ def financials( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def financials_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CompaniesFinancialsRecentResponse]: """ Get most recent financial statements for public companies. Convenience endpoint that automatically returns the latest available fiscal year data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesFinancialsRecentResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -273,9 +331,9 @@ def financials_recent( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesFinancialsRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesFinancialsRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -305,24 +363,29 @@ def financials_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CompaniesFinancingRecentResponse]: """ Get most recent funding rounds including round type, amount raised, investors, and valuation. Returns detailed information about the latest equity financing event. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesFinancingRecentResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -333,9 +396,9 @@ def financing_recent( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesFinancingRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesFinancingRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -376,24 +439,29 @@ def financing_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def active_investors( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Any]: + ) -> HttpResponse[CompaniesActiveInvestorsResponse]: """ Get current investors in the company from recent funding rounds. Returns investor names, types, and contact information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Any] + HttpResponse[CompaniesActiveInvestorsResponse] Active investors retrieved successfully """ _response = self._client_wrapper.httpx_client.request( @@ -401,74 +469,12 @@ def active_investors( method="GET", request_options=request_options, ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def all_investors( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete investor history including historical investors from all funding rounds. Returns comprehensive list of all investors who have participated in company financing. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/all-investors", - method="GET", - request_options=request_options, - ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesActiveInvestorsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesActiveInvestorsResponse, # type: ignore object_=_response.json(), ), ) @@ -498,24 +504,29 @@ def all_investors( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def deals( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Any]: + ) -> HttpResponse[CompaniesDealsResponse]: """ Get all deals for a company including funding rounds and acquisitions. Each deal includes the deal type, round name, investors involved, amount raised, and status. Funding rounds are sourced from investor data, while acquisitions are sourced from M&A records. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Any] + HttpResponse[CompaniesDealsResponse] Company deals retrieved successfully """ _response = self._client_wrapper.httpx_client.request( @@ -524,13 +535,11 @@ def deals( request_options=request_options, ) try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Any, + CompaniesDealsResponse, parse_obj_as( - type_=typing.Any, # type: ignore + type_=CompaniesDealsResponse, # type: ignore object_=_response.json(), ), ) @@ -560,24 +569,29 @@ def deals( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def service_providers( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CompaniesServiceProvidersResponse]: """ Get service providers working with the company including legal counsel, accounting firms, and consultants. Returns firm names, service types, and engagement information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesServiceProvidersResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -588,9 +602,9 @@ def service_providers( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesServiceProvidersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesServiceProvidersResponse, # type: ignore object_=_response.json(), ), ) @@ -631,24 +645,29 @@ def service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def service_providers_deal( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CompaniesServiceProvidersDealResponse]: """ Get service providers involved in specific financing deals including investment bankers, legal advisors, and financial consultants. Returns provider details specific to fundraising transactions. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesServiceProvidersDealResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -659,9 +678,9 @@ def service_providers_deal( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesServiceProvidersDealResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesServiceProvidersDealResponse, # type: ignore object_=_response.json(), ), ) @@ -702,24 +721,29 @@ def service_providers_deal( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def debt_financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CompaniesDebtFinancingRecentResponse]: """ Get most recent debt financing rounds including venture debt, credit lines, and loans. Returns lender information, amounts, and terms. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesDebtFinancingRecentResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -730,9 +754,9 @@ def debt_financing_recent( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesDebtFinancingRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesDebtFinancingRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -773,37 +797,52 @@ def debt_financing_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def similar( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + company_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CompaniesSimilarResponse]: """ Get companies similar to this one based on industry, size, and business model. Returns competitor and peer company information with similarity scores. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CompaniesSimilarResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/similar", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesSimilarResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesSimilarResponse, # type: ignore object_=_response.json(), ), ) @@ -844,85 +883,29 @@ def similar( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def social_analytics( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get social media metrics including LinkedIn followers, Twitter engagement, and Facebook presence. Useful for measuring company online visibility and growth. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/social-analytics", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def vc_exit_predictions( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Coming Soon: Get predicted likelihood and timeline for company exit events (IPO or acquisition). Will return ML-based exit probability scores and estimated exit valuation ranges when implemented. + **Coming Soon** - Get predicted likelihood and timeline for company exit events (IPO or acquisition). Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/vc-exit-predictions", @@ -931,14 +914,7 @@ def vc_exit_predictions( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -975,66 +951,10 @@ def vc_exit_predictions( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def updates( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to company profile data. Returns history of changes to company information with timestamps and modified fields. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -1043,32 +963,55 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def search( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CompaniesSearchResponse]: """ - Search for companies by name, industry, or location. Returns matching company profiles with employee count, industry classification, founding date, and headquarters. Use this to find company entity IDs for detailed lookups. + Search for companies by name or natural language description. + + ## Search Modes + + - **Direct lookup**: Short queries like `Stripe` or `OpenAI` resolve to a single company match + - **Natural language search**: Longer queries like `AI startups in San Francisco raising Series B` return up to 5 matching companies with surface-level data + + The endpoint auto-detects which mode to use based on the query. + + ## Response Fields + + Each result includes: name, website, description, employee_count, industry, location, founded, size, total_funding_raised, latest_funding_stage, tags, and linkedin_url. + + Use the entity_id or company identifier from results to call detail endpoints (bio, financing, investors, full). Parameters ---------- + q : str + Company name, domain, or natural language query (e.g. 'AI startups in San Francisco raising Series B') + + limit : typing.Optional[int] + Maximum results (default 5) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/companies/search", method="GET", + params={ + "q": q, + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -1087,54 +1030,50 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def bio( + async def full( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Any]: + ) -> AsyncHttpResponse[CompaniesFullResponse]: """ - Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. + Get the complete company record with ALL available data fields. + + Returns everything: base profile, funding details with amounts and investors, employee analytics and growth rates, executive changes, office locations, job postings, industry classifications, subsidiaries, and more. + + Use this after identifying a company via search to get the full picture. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Any] - Company bio retrieved successfully + AsyncHttpResponse[CompaniesFullResponse] + Complete company record """ _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/bio", + f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/full", method="GET", request_options=request_options, ) try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Any, + CompaniesFullResponse, parse_obj_as( - type_=typing.Any, # type: ignore + type_=CompaniesFullResponse, # type: ignore object_=_response.json(), ), ) return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) if _response.status_code == 404: raise NotFoundError( headers=dict(_response.headers), @@ -1149,37 +1088,42 @@ async def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def industries( + async def bio( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[CompaniesBioResponse]: """ - Get industry classifications including NAICS codes, SIC codes, and industry descriptions. Useful for filtering companies by vertical or sector. + Get comprehensive company profile including description, founding date, headquarters location, employee count, industry classification, and social media profiles. This is the primary endpoint for company overview data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[CompaniesBioResponse] + Company bio retrieved successfully """ _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/industries", + f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/bio", method="GET", request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesBioResponse, # type: ignore object_=_response.json(), ), ) @@ -1209,37 +1153,52 @@ async def industries( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def financials( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + company_id: str, + *, + fiscal_year: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CompaniesFinancialsResponse]: """ Get financial statements for public companies including revenue, net income, assets, and liabilities. Returns most recent fiscal year data or specific year if requested. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + + fiscal_year : typing.Optional[int] + Fiscal year (default: most recent) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesFinancialsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/financials", method="GET", + params={ + "fiscal_year": fiscal_year, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesFinancialsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesFinancialsResponse, # type: ignore object_=_response.json(), ), ) @@ -1269,24 +1228,29 @@ async def financials( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def financials_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[CompaniesFinancialsRecentResponse]: """ Get most recent financial statements for public companies. Convenience endpoint that automatically returns the latest available fiscal year data. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesFinancialsRecentResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1297,9 +1261,9 @@ async def financials_recent( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesFinancialsRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesFinancialsRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -1329,24 +1293,29 @@ async def financials_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[CompaniesFinancingRecentResponse]: """ Get most recent funding rounds including round type, amount raised, investors, and valuation. Returns detailed information about the latest equity financing event. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesFinancingRecentResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1357,9 +1326,9 @@ async def financing_recent( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesFinancingRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesFinancingRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -1400,24 +1369,29 @@ async def financing_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def active_investors( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Any]: + ) -> AsyncHttpResponse[CompaniesActiveInvestorsResponse]: """ Get current investors in the company from recent funding rounds. Returns investor names, types, and contact information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Any] + AsyncHttpResponse[CompaniesActiveInvestorsResponse] Active investors retrieved successfully """ _response = await self._client_wrapper.httpx_client.request( @@ -1425,74 +1399,12 @@ async def active_investors( method="GET", request_options=request_options, ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def all_investors( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete investor history including historical investors from all funding rounds. Returns comprehensive list of all investors who have participated in company financing. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/all-investors", - method="GET", - request_options=request_options, - ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesActiveInvestorsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesActiveInvestorsResponse, # type: ignore object_=_response.json(), ), ) @@ -1522,24 +1434,29 @@ async def all_investors( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def deals( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Any]: + ) -> AsyncHttpResponse[CompaniesDealsResponse]: """ Get all deals for a company including funding rounds and acquisitions. Each deal includes the deal type, round name, investors involved, amount raised, and status. Funding rounds are sourced from investor data, while acquisitions are sourced from M&A records. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Any] + AsyncHttpResponse[CompaniesDealsResponse] Company deals retrieved successfully """ _response = await self._client_wrapper.httpx_client.request( @@ -1548,13 +1465,11 @@ async def deals( request_options=request_options, ) try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Any, + CompaniesDealsResponse, parse_obj_as( - type_=typing.Any, # type: ignore + type_=CompaniesDealsResponse, # type: ignore object_=_response.json(), ), ) @@ -1584,24 +1499,29 @@ async def deals( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def service_providers( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[CompaniesServiceProvidersResponse]: """ Get service providers working with the company including legal counsel, accounting firms, and consultants. Returns firm names, service types, and engagement information. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesServiceProvidersResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1612,9 +1532,9 @@ async def service_providers( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesServiceProvidersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesServiceProvidersResponse, # type: ignore object_=_response.json(), ), ) @@ -1655,24 +1575,29 @@ async def service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def service_providers_deal( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[CompaniesServiceProvidersDealResponse]: """ Get service providers involved in specific financing deals including investment bankers, legal advisors, and financial consultants. Returns provider details specific to fundraising transactions. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesServiceProvidersDealResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1683,9 +1608,9 @@ async def service_providers_deal( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesServiceProvidersDealResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesServiceProvidersDealResponse, # type: ignore object_=_response.json(), ), ) @@ -1726,24 +1651,29 @@ async def service_providers_deal( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def debt_financing_recent( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[CompaniesDebtFinancingRecentResponse]: """ Get most recent debt financing rounds including venture debt, credit lines, and loans. Returns lender information, amounts, and terms. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesDebtFinancingRecentResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1754,9 +1684,9 @@ async def debt_financing_recent( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesDebtFinancingRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesDebtFinancingRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -1797,37 +1727,52 @@ async def debt_financing_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def similar( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + company_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CompaniesSimilarResponse]: """ Get companies similar to this one based on industry, size, and business model. Returns competitor and peer company information with similarity scores. Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[CompaniesSimilarResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/similar", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CompaniesSimilarResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CompaniesSimilarResponse, # type: ignore object_=_response.json(), ), ) @@ -1868,85 +1813,29 @@ async def similar( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def social_analytics( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get social media metrics including LinkedIn followers, Twitter engagement, and Facebook presence. Useful for measuring company online visibility and growth. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/social-analytics", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def vc_exit_predictions( self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get predicted likelihood and timeline for company exit events (IPO or acquisition). Will return ML-based exit probability scores and estimated exit valuation ranges when implemented. + **Coming Soon** - Get predicted likelihood and timeline for company exit events (IPO or acquisition). Parameters ---------- company_id : str + Company entity ID, website domain, or company name (e.g., 'openai.com', 'OpenAI', or UUID) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/vc-exit-predictions", @@ -1955,14 +1844,7 @@ async def vc_exit_predictions( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1999,64 +1881,8 @@ async def vc_exit_predictions( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def updates( - self, company_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to company profile data. Returns history of changes to company information with timestamps and modified fields. - - Parameters - ---------- - company_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/companies/{jsonable_encoder(company_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/companies/types/__init__.py b/src/runcaptain/companies/types/__init__.py new file mode 100644 index 0000000..1f48c08 --- /dev/null +++ b/src/runcaptain/companies/types/__init__.py @@ -0,0 +1,123 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .companies_active_investors_response import CompaniesActiveInvestorsResponse + from .companies_active_investors_response_investors_item import CompaniesActiveInvestorsResponseInvestorsItem + from .companies_bio_response import CompaniesBioResponse + from .companies_bio_response_headquarters import CompaniesBioResponseHeadquarters + from .companies_bio_response_social_profiles import CompaniesBioResponseSocialProfiles + from .companies_deals_response import CompaniesDealsResponse + from .companies_deals_response_deals_item import CompaniesDealsResponseDealsItem + from .companies_debt_financing_recent_response import CompaniesDebtFinancingRecentResponse + from .companies_debt_financing_recent_response_round import CompaniesDebtFinancingRecentResponseRound + from .companies_financials_recent_response import CompaniesFinancialsRecentResponse + from .companies_financials_response import CompaniesFinancialsResponse + from .companies_financing_recent_response import CompaniesFinancingRecentResponse + from .companies_financing_recent_response_rounds_item import CompaniesFinancingRecentResponseRoundsItem + from .companies_financing_recent_response_web_sources_item import CompaniesFinancingRecentResponseWebSourcesItem + from .companies_full_response import CompaniesFullResponse + from .companies_full_response_affiliated_entities_item import CompaniesFullResponseAffiliatedEntitiesItem + from .companies_full_response_funding_details_item import CompaniesFullResponseFundingDetailsItem + from .companies_full_response_headquarters import CompaniesFullResponseHeadquarters + from .companies_full_response_location import CompaniesFullResponseLocation + from .companies_full_response_social_profiles import CompaniesFullResponseSocialProfiles + from .companies_search_response import CompaniesSearchResponse + from .companies_search_response_results_item import CompaniesSearchResponseResultsItem + from .companies_service_providers_deal_response import CompaniesServiceProvidersDealResponse + from .companies_service_providers_deal_response_service_providers_item import ( + CompaniesServiceProvidersDealResponseServiceProvidersItem, + ) + from .companies_service_providers_response import CompaniesServiceProvidersResponse + from .companies_service_providers_response_service_providers_item import ( + CompaniesServiceProvidersResponseServiceProvidersItem, + ) + from .companies_similar_response import CompaniesSimilarResponse + from .companies_similar_response_competitors_item import CompaniesSimilarResponseCompetitorsItem +_dynamic_imports: typing.Dict[str, str] = { + "CompaniesActiveInvestorsResponse": ".companies_active_investors_response", + "CompaniesActiveInvestorsResponseInvestorsItem": ".companies_active_investors_response_investors_item", + "CompaniesBioResponse": ".companies_bio_response", + "CompaniesBioResponseHeadquarters": ".companies_bio_response_headquarters", + "CompaniesBioResponseSocialProfiles": ".companies_bio_response_social_profiles", + "CompaniesDealsResponse": ".companies_deals_response", + "CompaniesDealsResponseDealsItem": ".companies_deals_response_deals_item", + "CompaniesDebtFinancingRecentResponse": ".companies_debt_financing_recent_response", + "CompaniesDebtFinancingRecentResponseRound": ".companies_debt_financing_recent_response_round", + "CompaniesFinancialsRecentResponse": ".companies_financials_recent_response", + "CompaniesFinancialsResponse": ".companies_financials_response", + "CompaniesFinancingRecentResponse": ".companies_financing_recent_response", + "CompaniesFinancingRecentResponseRoundsItem": ".companies_financing_recent_response_rounds_item", + "CompaniesFinancingRecentResponseWebSourcesItem": ".companies_financing_recent_response_web_sources_item", + "CompaniesFullResponse": ".companies_full_response", + "CompaniesFullResponseAffiliatedEntitiesItem": ".companies_full_response_affiliated_entities_item", + "CompaniesFullResponseFundingDetailsItem": ".companies_full_response_funding_details_item", + "CompaniesFullResponseHeadquarters": ".companies_full_response_headquarters", + "CompaniesFullResponseLocation": ".companies_full_response_location", + "CompaniesFullResponseSocialProfiles": ".companies_full_response_social_profiles", + "CompaniesSearchResponse": ".companies_search_response", + "CompaniesSearchResponseResultsItem": ".companies_search_response_results_item", + "CompaniesServiceProvidersDealResponse": ".companies_service_providers_deal_response", + "CompaniesServiceProvidersDealResponseServiceProvidersItem": ".companies_service_providers_deal_response_service_providers_item", + "CompaniesServiceProvidersResponse": ".companies_service_providers_response", + "CompaniesServiceProvidersResponseServiceProvidersItem": ".companies_service_providers_response_service_providers_item", + "CompaniesSimilarResponse": ".companies_similar_response", + "CompaniesSimilarResponseCompetitorsItem": ".companies_similar_response_competitors_item", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CompaniesActiveInvestorsResponse", + "CompaniesActiveInvestorsResponseInvestorsItem", + "CompaniesBioResponse", + "CompaniesBioResponseHeadquarters", + "CompaniesBioResponseSocialProfiles", + "CompaniesDealsResponse", + "CompaniesDealsResponseDealsItem", + "CompaniesDebtFinancingRecentResponse", + "CompaniesDebtFinancingRecentResponseRound", + "CompaniesFinancialsRecentResponse", + "CompaniesFinancialsResponse", + "CompaniesFinancingRecentResponse", + "CompaniesFinancingRecentResponseRoundsItem", + "CompaniesFinancingRecentResponseWebSourcesItem", + "CompaniesFullResponse", + "CompaniesFullResponseAffiliatedEntitiesItem", + "CompaniesFullResponseFundingDetailsItem", + "CompaniesFullResponseHeadquarters", + "CompaniesFullResponseLocation", + "CompaniesFullResponseSocialProfiles", + "CompaniesSearchResponse", + "CompaniesSearchResponseResultsItem", + "CompaniesServiceProvidersDealResponse", + "CompaniesServiceProvidersDealResponseServiceProvidersItem", + "CompaniesServiceProvidersResponse", + "CompaniesServiceProvidersResponseServiceProvidersItem", + "CompaniesSimilarResponse", + "CompaniesSimilarResponseCompetitorsItem", +] diff --git a/src/runcaptain/companies/types/companies_active_investors_response.py b/src/runcaptain/companies/types/companies_active_investors_response.py new file mode 100644 index 0000000..3043314 --- /dev/null +++ b/src/runcaptain/companies/types/companies_active_investors_response.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_active_investors_response_investors_item import CompaniesActiveInvestorsResponseInvestorsItem + + +class CompaniesActiveInvestorsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + investors: typing.Optional[typing.List[CompaniesActiveInvestorsResponseInvestorsItem]] = pydantic.Field( + default=None + ) + """ + Active investors + """ + + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of investors + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_active_investors_response_investors_item.py b/src/runcaptain/companies/types/companies_active_investors_response_investors_item.py new file mode 100644 index 0000000..162f005 --- /dev/null +++ b/src/runcaptain/companies/types/companies_active_investors_response_investors_item.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesActiveInvestorsResponseInvestorsItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + investor_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor type (venture_capital, corporate, individual) + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor website + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Brief description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_bio_response.py b/src/runcaptain/companies/types/companies_bio_response.py new file mode 100644 index 0000000..d0673ef --- /dev/null +++ b/src/runcaptain/companies/types/companies_bio_response.py @@ -0,0 +1,76 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_bio_response_headquarters import CompaniesBioResponseHeadquarters +from .companies_bio_response_social_profiles import CompaniesBioResponseSocialProfiles + + +class CompaniesBioResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Always 'company' + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company display name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Company website + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Company description + """ + + founded: typing.Optional[int] = pydantic.Field(default=None) + """ + Year founded + """ + + headquarters: typing.Optional[CompaniesBioResponseHeadquarters] = None + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Primary industry + """ + + company_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Company type (private, public) + """ + + social_profiles: typing.Optional[CompaniesBioResponseSocialProfiles] = None + data_sources: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Data sources used + """ + + cached_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Cache timestamp (ISO 8601) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_bio_response_headquarters.py b/src/runcaptain/companies/types/companies_bio_response_headquarters.py new file mode 100644 index 0000000..6381974 --- /dev/null +++ b/src/runcaptain/companies/types/companies_bio_response_headquarters.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesBioResponseHeadquarters(UniversalBaseModel): + city: typing.Optional[str] = None + state: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_bio_response_social_profiles.py b/src/runcaptain/companies/types/companies_bio_response_social_profiles.py new file mode 100644 index 0000000..38c0969 --- /dev/null +++ b/src/runcaptain/companies/types/companies_bio_response_social_profiles.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesBioResponseSocialProfiles(UniversalBaseModel): + linkedin: typing.Optional[str] = None + twitter: typing.Optional[str] = None + facebook: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_deals_response.py b/src/runcaptain/companies/types/companies_deals_response.py new file mode 100644 index 0000000..bcd1fa2 --- /dev/null +++ b/src/runcaptain/companies/types/companies_deals_response.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_deals_response_deals_item import CompaniesDealsResponseDealsItem + + +class CompaniesDealsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + deals: typing.Optional[typing.List[CompaniesDealsResponseDealsItem]] = pydantic.Field(default=None) + """ + Deals and funding rounds + """ + + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of deals + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_deals_response_deals_item.py b/src/runcaptain/companies/types/companies_deals_response_deals_item.py new file mode 100644 index 0000000..fffc387 --- /dev/null +++ b/src/runcaptain/companies/types/companies_deals_response_deals_item.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesDealsResponseDealsItem(UniversalBaseModel): + deal_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal type (funding_round, acquisition) + """ + + round_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Round name + """ + + investors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Investor names + """ + + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Deal amount in USD + """ + + date: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal date (YYYY-MM-DD) + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal status (completed, pending) + """ + + target_company: typing.Optional[str] = pydantic.Field(default=None) + """ + Acquisition target (for acquisitions) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_debt_financing_recent_response.py b/src/runcaptain/companies/types/companies_debt_financing_recent_response.py new file mode 100644 index 0000000..35190ff --- /dev/null +++ b/src/runcaptain/companies/types/companies_debt_financing_recent_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_debt_financing_recent_response_round import CompaniesDebtFinancingRecentResponseRound + + +class CompaniesDebtFinancingRecentResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + round: typing.Optional[CompaniesDebtFinancingRecentResponseRound] = pydantic.Field(default=None) + """ + Most recent debt financing round (null if none) + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_debt_financing_recent_response_round.py b/src/runcaptain/companies/types/companies_debt_financing_recent_response_round.py new file mode 100644 index 0000000..2326e8c --- /dev/null +++ b/src/runcaptain/companies/types/companies_debt_financing_recent_response_round.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesDebtFinancingRecentResponseRound(UniversalBaseModel): + """ + Most recent debt financing round (null if none) + """ + + round_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Financing type + """ + + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Amount in USD + """ + + date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date (YYYY-MM-DD) + """ + + lenders: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Lender names + """ + + currency: typing.Optional[str] = pydantic.Field(default=None) + """ + Currency code (default USD) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_financials_recent_response.py b/src/runcaptain/companies/types/companies_financials_recent_response.py new file mode 100644 index 0000000..a29dd94 --- /dev/null +++ b/src/runcaptain/companies/types/companies_financials_recent_response.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFinancialsRecentResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + revenue: typing.Optional[float] = pydantic.Field(default=None) + """ + Annual revenue in USD + """ + + inferred_revenue: typing.Optional[str] = pydantic.Field(default=None) + """ + Inferred revenue range for private companies + """ + + net_income: typing.Optional[float] = pydantic.Field(default=None) + """ + Net income in USD + """ + + assets: typing.Optional[float] = pydantic.Field(default=None) + """ + Total assets in USD + """ + + liabilities: typing.Optional[float] = pydantic.Field(default=None) + """ + Total liabilities in USD + """ + + fiscal_year: typing.Optional[int] = pydantic.Field(default=None) + """ + Fiscal year + """ + + filing_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Filing date (YYYY-MM-DD) + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Data source + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_financials_response.py b/src/runcaptain/companies/types/companies_financials_response.py new file mode 100644 index 0000000..eefb08e --- /dev/null +++ b/src/runcaptain/companies/types/companies_financials_response.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFinancialsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + revenue: typing.Optional[float] = pydantic.Field(default=None) + """ + Annual revenue in USD (public companies via SEC) + """ + + inferred_revenue: typing.Optional[str] = pydantic.Field(default=None) + """ + Inferred revenue range for private companies + """ + + net_income: typing.Optional[float] = pydantic.Field(default=None) + """ + Net income in USD + """ + + assets: typing.Optional[float] = pydantic.Field(default=None) + """ + Total assets in USD + """ + + liabilities: typing.Optional[float] = pydantic.Field(default=None) + """ + Total liabilities in USD + """ + + fiscal_year: typing.Optional[int] = pydantic.Field(default=None) + """ + Fiscal year + """ + + filing_date: typing.Optional[str] = pydantic.Field(default=None) + """ + SEC filing date (YYYY-MM-DD) + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Data source (sec_edgar or inferred) + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_financing_recent_response.py b/src/runcaptain/companies/types/companies_financing_recent_response.py new file mode 100644 index 0000000..ec7472b --- /dev/null +++ b/src/runcaptain/companies/types/companies_financing_recent_response.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_financing_recent_response_rounds_item import CompaniesFinancingRecentResponseRoundsItem +from .companies_financing_recent_response_web_sources_item import CompaniesFinancingRecentResponseWebSourcesItem + + +class CompaniesFinancingRecentResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + total_raised: typing.Optional[float] = pydantic.Field(default=None) + """ + Total funding raised in USD + """ + + num_rounds: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of funding rounds + """ + + latest_stage: typing.Optional[str] = pydantic.Field(default=None) + """ + Most recent funding stage + """ + + last_funding_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date of last funding (YYYY-MM-DD) + """ + + rounds: typing.Optional[typing.List[CompaniesFinancingRecentResponseRoundsItem]] = pydantic.Field(default=None) + """ + Funding rounds + """ + + web_sources: typing.Optional[typing.List[CompaniesFinancingRecentResponseWebSourcesItem]] = pydantic.Field( + default=None + ) + """ + Web sources for funding data + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_financing_recent_response_rounds_item.py b/src/runcaptain/companies/types/companies_financing_recent_response_rounds_item.py new file mode 100644 index 0000000..fcfde82 --- /dev/null +++ b/src/runcaptain/companies/types/companies_financing_recent_response_rounds_item.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFinancingRecentResponseRoundsItem(UniversalBaseModel): + round_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Round type (e.g., Series A) + """ + + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Amount raised in USD + """ + + date: typing.Optional[str] = pydantic.Field(default=None) + """ + Round date (YYYY-MM-DD) + """ + + investors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Investor names + """ + + valuation: typing.Optional[float] = pydantic.Field(default=None) + """ + Valuation at round + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_financing_recent_response_web_sources_item.py b/src/runcaptain/companies/types/companies_financing_recent_response_web_sources_item.py new file mode 100644 index 0000000..dd9428c --- /dev/null +++ b/src/runcaptain/companies/types/companies_financing_recent_response_web_sources_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFinancingRecentResponseWebSourcesItem(UniversalBaseModel): + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Source title + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Source URL + """ + + snippet: typing.Optional[str] = pydantic.Field(default=None) + """ + Source excerpt + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_full_response.py b/src/runcaptain/companies/types/companies_full_response.py new file mode 100644 index 0000000..9d2a58e --- /dev/null +++ b/src/runcaptain/companies/types/companies_full_response.py @@ -0,0 +1,155 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_full_response_affiliated_entities_item import CompaniesFullResponseAffiliatedEntitiesItem +from .companies_full_response_funding_details_item import CompaniesFullResponseFundingDetailsItem +from .companies_full_response_headquarters import CompaniesFullResponseHeadquarters +from .companies_full_response_location import CompaniesFullResponseLocation +from .companies_full_response_social_profiles import CompaniesFullResponseSocialProfiles + + +class CompaniesFullResponse(UniversalBaseModel): + """ + Complete company profile combining bio, financials, funding, investors, deals, and social analytics. + """ + + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Always 'company' + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company display name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Company website + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Company description + """ + + founded: typing.Optional[int] = pydantic.Field(default=None) + """ + Year founded + """ + + headquarters: typing.Optional[CompaniesFullResponseHeadquarters] = None + employee_count: typing.Optional[int] = None + industry: typing.Optional[str] = None + company_type: typing.Optional[str] = None + financials: typing.Optional[typing.Dict[str, typing.Any]] = None + funding: typing.Optional[typing.Dict[str, typing.Any]] = None + investors: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + deals: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + social_analytics: typing.Optional[typing.Dict[str, typing.Any]] = None + data_sources: typing.Optional[typing.List[str]] = None + industry_v2: typing.Optional[str] = pydantic.Field(default=None) + """ + Detailed industry classification + """ + + size: typing.Optional[str] = pydantic.Field(default=None) + """ + Company size range (e.g., 1001-5000) + """ + + inferred_revenue: typing.Optional[str] = pydantic.Field(default=None) + """ + Estimated revenue range for private companies + """ + + linkedin_follower_count: typing.Optional[int] = pydantic.Field(default=None) + """ + LinkedIn follower count + """ + + total_funding_raised: typing.Optional[float] = pydantic.Field(default=None) + """ + Total funding raised in USD + """ + + latest_funding_stage: typing.Optional[str] = pydantic.Field(default=None) + """ + Most recent funding round type + """ + + last_funding_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date of most recent funding round + """ + + number_funding_rounds: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of funding rounds + """ + + headline: typing.Optional[str] = pydantic.Field(default=None) + """ + Company tagline/headline + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Industry and sector tags + """ + + location: typing.Optional[CompaniesFullResponseLocation] = pydantic.Field(default=None) + """ + Detailed location object + """ + + social_profiles: typing.Optional[CompaniesFullResponseSocialProfiles] = pydantic.Field(default=None) + """ + Social media profile URLs + """ + + funding_details: typing.Optional[typing.List[CompaniesFullResponseFundingDetailsItem]] = pydantic.Field( + default=None + ) + """ + Detailed funding round history + """ + + affiliated_entities: typing.Optional[typing.List[CompaniesFullResponseAffiliatedEntitiesItem]] = pydantic.Field( + default=None + ) + """ + Parent/subsidiary relationships + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn company URL + """ + + facebook_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Facebook page URL + """ + + twitter_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Twitter/X URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_full_response_affiliated_entities_item.py b/src/runcaptain/companies/types/companies_full_response_affiliated_entities_item.py new file mode 100644 index 0000000..77d49a1 --- /dev/null +++ b/src/runcaptain/companies/types/companies_full_response_affiliated_entities_item.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFullResponseAffiliatedEntitiesItem(UniversalBaseModel): + display_name: typing.Optional[str] = None + relationship: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_full_response_funding_details_item.py b/src/runcaptain/companies/types/companies_full_response_funding_details_item.py new file mode 100644 index 0000000..d81bb18 --- /dev/null +++ b/src/runcaptain/companies/types/companies_full_response_funding_details_item.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFullResponseFundingDetailsItem(UniversalBaseModel): + funding_type: typing.Optional[str] = None + funding_raised: typing.Optional[float] = None + funding_round_date: typing.Optional[str] = None + funding_currency: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_full_response_headquarters.py b/src/runcaptain/companies/types/companies_full_response_headquarters.py new file mode 100644 index 0000000..60925f5 --- /dev/null +++ b/src/runcaptain/companies/types/companies_full_response_headquarters.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFullResponseHeadquarters(UniversalBaseModel): + city: typing.Optional[str] = None + state: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_full_response_location.py b/src/runcaptain/companies/types/companies_full_response_location.py new file mode 100644 index 0000000..a7b19e6 --- /dev/null +++ b/src/runcaptain/companies/types/companies_full_response_location.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFullResponseLocation(UniversalBaseModel): + """ + Detailed location object + """ + + name: typing.Optional[str] = None + locality: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + continent: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_full_response_social_profiles.py b/src/runcaptain/companies/types/companies_full_response_social_profiles.py new file mode 100644 index 0000000..109c406 --- /dev/null +++ b/src/runcaptain/companies/types/companies_full_response_social_profiles.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesFullResponseSocialProfiles(UniversalBaseModel): + """ + Social media profile URLs + """ + + linkedin: typing.Optional[str] = None + twitter: typing.Optional[str] = None + facebook: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_search_response.py b/src/runcaptain/companies/types/companies_search_response.py new file mode 100644 index 0000000..2821947 --- /dev/null +++ b/src/runcaptain/companies/types/companies_search_response.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_search_response_results_item import CompaniesSearchResponseResultsItem + + +class CompaniesSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[CompaniesSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum results requested + """ + + search_mode: typing.Optional[str] = pydantic.Field(default=None) + """ + Search mode used (direct or natural_language) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_search_response_results_item.py b/src/runcaptain/companies/types/companies_search_response_results_item.py new file mode 100644 index 0000000..1074a21 --- /dev/null +++ b/src/runcaptain/companies/types/companies_search_response_results_item.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesSearchResponseResultsItem(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Company website domain + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Brief company description + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Primary industry + """ + + location: typing.Optional[str] = pydantic.Field(default=None) + """ + Headquarters location + """ + + founded: typing.Optional[int] = pydantic.Field(default=None) + """ + Year founded + """ + + size: typing.Optional[str] = pydantic.Field(default=None) + """ + Company size range + """ + + total_funding_raised: typing.Optional[float] = pydantic.Field(default=None) + """ + Total funding raised in USD + """ + + latest_funding_stage: typing.Optional[str] = pydantic.Field(default=None) + """ + Most recent funding round type + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Industry and technology tags + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn company URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_service_providers_deal_response.py b/src/runcaptain/companies/types/companies_service_providers_deal_response.py new file mode 100644 index 0000000..b6290ce --- /dev/null +++ b/src/runcaptain/companies/types/companies_service_providers_deal_response.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_service_providers_deal_response_service_providers_item import ( + CompaniesServiceProvidersDealResponseServiceProvidersItem, +) + + +class CompaniesServiceProvidersDealResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + service_providers: typing.Optional[typing.List[CompaniesServiceProvidersDealResponseServiceProvidersItem]] = ( + pydantic.Field(default=None) + ) + """ + Deal-specific service providers + """ + + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total deal service providers + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_service_providers_deal_response_service_providers_item.py b/src/runcaptain/companies/types/companies_service_providers_deal_response_service_providers_item.py new file mode 100644 index 0000000..dcf5547 --- /dev/null +++ b/src/runcaptain/companies/types/companies_service_providers_deal_response_service_providers_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesServiceProvidersDealResponseServiceProvidersItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider name + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service type + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Role in deal + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_service_providers_response.py b/src/runcaptain/companies/types/companies_service_providers_response.py new file mode 100644 index 0000000..3f2d5f4 --- /dev/null +++ b/src/runcaptain/companies/types/companies_service_providers_response.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_service_providers_response_service_providers_item import ( + CompaniesServiceProvidersResponseServiceProvidersItem, +) + + +class CompaniesServiceProvidersResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + service_providers: typing.Optional[typing.List[CompaniesServiceProvidersResponseServiceProvidersItem]] = ( + pydantic.Field(default=None) + ) + """ + Service providers + """ + + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total service providers + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_service_providers_response_service_providers_item.py b/src/runcaptain/companies/types/companies_service_providers_response_service_providers_item.py new file mode 100644 index 0000000..71c3e97 --- /dev/null +++ b/src/runcaptain/companies/types/companies_service_providers_response_service_providers_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesServiceProvidersResponseServiceProvidersItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider name + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service type (law, accounting, investment_bank) + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Role in engagement + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_similar_response.py b/src/runcaptain/companies/types/companies_similar_response.py new file mode 100644 index 0000000..cafaae9 --- /dev/null +++ b/src/runcaptain/companies/types/companies_similar_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .companies_similar_response_competitors_item import CompaniesSimilarResponseCompetitorsItem + + +class CompaniesSimilarResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + competitors: typing.Optional[typing.List[CompaniesSimilarResponseCompetitorsItem]] = pydantic.Field(default=None) + """ + Similar companies + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/companies/types/companies_similar_response_competitors_item.py b/src/runcaptain/companies/types/companies_similar_response_competitors_item.py new file mode 100644 index 0000000..43c3c70 --- /dev/null +++ b/src/runcaptain/companies/types/companies_similar_response_competitors_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CompaniesSimilarResponseCompetitorsItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + similarity_score: typing.Optional[float] = pydantic.Field(default=None) + """ + Similarity score (0-1) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/core/__init__.py b/src/runcaptain/core/__init__.py index 75b240c..4fb6e12 100644 --- a/src/runcaptain/core/__init__.py +++ b/src/runcaptain/core/__init__.py @@ -8,12 +8,13 @@ if typing.TYPE_CHECKING: from .api_error import ApiError from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper - from .datetime_utils import serialize_datetime + from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime from .file import File, convert_file_dict_to_httpx_tuples, with_content_type from .http_client import AsyncHttpClient, HttpClient from .http_response import AsyncHttpResponse, HttpResponse from .jsonable_encoder import jsonable_encoder from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger + from .parse_error import ParsingError from .pydantic_utilities import ( IS_PYDANTIC_V2, UniversalBaseModel, @@ -43,7 +44,9 @@ "LogConfig": ".logging", "LogLevel": ".logging", "Logger": ".logging", + "ParsingError": ".parse_error", "RequestOptions": ".request_options", + "Rfc2822DateTime": ".datetime_utils", "SyncClientWrapper": ".client_wrapper", "UniversalBaseModel": ".pydantic_utilities", "UniversalRootModel": ".pydantic_utilities", @@ -53,6 +56,7 @@ "encode_query": ".query_encoder", "jsonable_encoder": ".jsonable_encoder", "parse_obj_as": ".pydantic_utilities", + "parse_rfc2822_datetime": ".datetime_utils", "remove_none_from_dict": ".remove_none_from_dict", "serialize_datetime": ".datetime_utils", "universal_field_validator": ".pydantic_utilities", @@ -99,7 +103,9 @@ def __dir__(): "LogConfig", "LogLevel", "Logger", + "ParsingError", "RequestOptions", + "Rfc2822DateTime", "SyncClientWrapper", "UniversalBaseModel", "UniversalRootModel", @@ -109,6 +115,7 @@ def __dir__(): "encode_query", "jsonable_encoder", "parse_obj_as", + "parse_rfc2822_datetime", "remove_none_from_dict", "serialize_datetime", "universal_field_validator", diff --git a/src/runcaptain/core/client_wrapper.py b/src/runcaptain/core/client_wrapper.py index 4adac0a..7667c56 100644 --- a/src/runcaptain/core/client_wrapper.py +++ b/src/runcaptain/core/client_wrapper.py @@ -29,12 +29,12 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "captain-sdk/0.1.1", + "User-Agent": "captain-sdk/0.1.2", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", "X-Fern-SDK-Name": "captain-sdk", - "X-Fern-SDK-Version": "0.1.1", + "X-Fern-SDK-Version": "0.1.2", **(self.get_custom_headers() or {}), } headers["X-Organization-ID"] = self._organization_id diff --git a/src/runcaptain/core/datetime_utils.py b/src/runcaptain/core/datetime_utils.py index 7c9864a..a12b2ad 100644 --- a/src/runcaptain/core/datetime_utils.py +++ b/src/runcaptain/core/datetime_utils.py @@ -1,6 +1,48 @@ # This file was auto-generated by Fern from our API Definition. import datetime as dt +from email.utils import parsedate_to_datetime +from typing import Any + +import pydantic + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + + +def parse_rfc2822_datetime(v: Any) -> dt.datetime: + """ + Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") + into a datetime object. If the value is already a datetime, return it as-is. + Falls back to ISO 8601 parsing if RFC 2822 parsing fails. + """ + if isinstance(v, dt.datetime): + return v + if isinstance(v, str): + try: + return parsedate_to_datetime(v) + except Exception: + pass + # Fallback to ISO 8601 parsing + return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) + raise ValueError(f"Expected str or datetime, got {type(v)}") + + +class Rfc2822DateTime(dt.datetime): + """A datetime subclass that parses RFC 2822 date strings. + + On Pydantic V1, uses __get_validators__ for pre-validation. + On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. + """ + + @classmethod + def __get_validators__(cls): # type: ignore[no-untyped-def] + yield parse_rfc2822_datetime + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] + from pydantic_core import core_schema + + return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) def serialize_datetime(v: dt.datetime) -> str: diff --git a/src/runcaptain/core/parse_error.py b/src/runcaptain/core/parse_error.py new file mode 100644 index 0000000..4527c6a --- /dev/null +++ b/src/runcaptain/core/parse_error.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ParsingError(Exception): + """ + Raised when the SDK fails to parse/validate a response from the server. + This typically indicates that the server returned a response whose shape + does not match the expected schema. + """ + + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + cause: Optional[Exception] + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + cause: Optional[Exception] = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + self.cause = cause + super().__init__() + if cause is not None: + self.__cause__ = cause + + def __str__(self) -> str: + cause_str = f", cause: {self.cause}" if self.cause is not None else "" + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/src/runcaptain/core/pydantic_utilities.py b/src/runcaptain/core/pydantic_utilities.py index 831aadc..fea3a08 100644 --- a/src/runcaptain/core/pydantic_utilities.py +++ b/src/runcaptain/core/pydantic_utilities.py @@ -26,6 +26,7 @@ import pydantic import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo _logger = logging.getLogger(__name__) @@ -35,8 +36,6 @@ IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") if IS_PYDANTIC_V2: - import warnings - _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] @@ -52,22 +51,80 @@ def parse_date(value: Any) -> dt.date: # type: ignore[misc] return value return _date_adapter.validate_python(value) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", UserWarning) - from pydantic.v1.fields import ModelField as ModelField - from pydantic.v1.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[attr-defined] - from pydantic.v1.typing import get_args as get_args - from pydantic.v1.typing import get_origin as get_origin - from pydantic.v1.typing import is_literal_type as is_literal_type - from pydantic.v1.typing import is_union as is_union + # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. + from typing import get_args as get_args # type: ignore[assignment] + from typing import get_origin as get_origin # type: ignore[assignment] + + def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return typing_extensions.get_origin(tp) is typing_extensions.Literal + + def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] + + # Inline encoders_by_type to avoid importing from pydantic.v1.json + import re as _re + from collections import deque as _deque + from decimal import Decimal as _Decimal + from enum import Enum as _Enum + from ipaddress import ( + IPv4Address as _IPv4Address, + ) + from ipaddress import ( + IPv4Interface as _IPv4Interface, + ) + from ipaddress import ( + IPv4Network as _IPv4Network, + ) + from ipaddress import ( + IPv6Address as _IPv6Address, + ) + from ipaddress import ( + IPv6Interface as _IPv6Interface, + ) + from ipaddress import ( + IPv6Network as _IPv6Network, + ) + from pathlib import Path as _Path + from types import GeneratorType as _GeneratorType + from uuid import UUID as _UUID + + from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] + + def _decimal_encoder(dec_value: Any) -> Any: + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + return float(dec_value) + + encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] + bytes: lambda o: o.decode(), + dt.date: lambda o: o.isoformat(), + dt.datetime: lambda o: o.isoformat(), + dt.time: lambda o: o.isoformat(), + dt.timedelta: lambda td: td.total_seconds(), + _Decimal: _decimal_encoder, + _Enum: lambda o: o.value, + frozenset: list, + _deque: list, + _GeneratorType: list, + _IPv4Address: str, + _IPv4Interface: str, + _IPv4Network: str, + _IPv6Address: str, + _IPv6Interface: str, + _IPv6Network: str, + _Path: str, + _re.Pattern: lambda o: o.pattern, + set: list, + _UUID: str, + } else: from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] - from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] from pydantic.typing import get_args as get_args # type: ignore[no-redef] from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] - from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] from pydantic.typing import is_union as is_union # type: ignore[no-redef] from .datetime_utils import serialize_datetime @@ -554,7 +611,7 @@ def decorator(func: AnyCallable) -> AnyCallable: return decorator -PydanticField = Union[ModelField, pydantic.fields.FieldInfo] +PydanticField = Union[ModelField, _FieldInfo] def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: diff --git a/src/runcaptain/credit_analysis/__init__.py b/src/runcaptain/credit_analysis/__init__.py index 5cde020..5bdcefc 100644 --- a/src/runcaptain/credit_analysis/__init__.py +++ b/src/runcaptain/credit_analysis/__init__.py @@ -2,3 +2,66 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreditAnalysisBdcSearchRequestSeniority, + CreditAnalysisFundsSearchRequestStrategy, + CreditAnalysisNewsBulkResponse, + CreditAnalysisNewsBulkResponseResultsItem, + CreditAnalysisNewsDetailResponse, + CreditAnalysisNewsRecentResponse, + CreditAnalysisNewsRecentResponseResultsItem, + CreditAnalysisNewsSearchResponse, + CreditAnalysisNewsSearchResponseResultsItem, + CreditAnalysisSbaSearchRequestStatus, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreditAnalysisBdcSearchRequestSeniority": ".types", + "CreditAnalysisFundsSearchRequestStrategy": ".types", + "CreditAnalysisNewsBulkResponse": ".types", + "CreditAnalysisNewsBulkResponseResultsItem": ".types", + "CreditAnalysisNewsDetailResponse": ".types", + "CreditAnalysisNewsRecentResponse": ".types", + "CreditAnalysisNewsRecentResponseResultsItem": ".types", + "CreditAnalysisNewsSearchResponse": ".types", + "CreditAnalysisNewsSearchResponseResultsItem": ".types", + "CreditAnalysisSbaSearchRequestStatus": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreditAnalysisBdcSearchRequestSeniority", + "CreditAnalysisFundsSearchRequestStrategy", + "CreditAnalysisNewsBulkResponse", + "CreditAnalysisNewsBulkResponseResultsItem", + "CreditAnalysisNewsDetailResponse", + "CreditAnalysisNewsRecentResponse", + "CreditAnalysisNewsRecentResponseResultsItem", + "CreditAnalysisNewsSearchResponse", + "CreditAnalysisNewsSearchResponseResultsItem", + "CreditAnalysisSbaSearchRequestStatus", +] diff --git a/src/runcaptain/credit_analysis/client.py b/src/runcaptain/credit_analysis/client.py index 77704ff..0c023e6 100644 --- a/src/runcaptain/credit_analysis/client.py +++ b/src/runcaptain/credit_analysis/client.py @@ -5,6 +5,13 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawCreditAnalysisClient, RawCreditAnalysisClient +from .types.credit_analysis_bdc_search_request_seniority import CreditAnalysisBdcSearchRequestSeniority +from .types.credit_analysis_funds_search_request_strategy import CreditAnalysisFundsSearchRequestStrategy +from .types.credit_analysis_news_bulk_response import CreditAnalysisNewsBulkResponse +from .types.credit_analysis_news_detail_response import CreditAnalysisNewsDetailResponse +from .types.credit_analysis_news_recent_response import CreditAnalysisNewsRecentResponse +from .types.credit_analysis_news_search_response import CreditAnalysisNewsSearchResponse +from .types.credit_analysis_sba_search_request_status import CreditAnalysisSbaSearchRequestStatus # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -28,16 +35,28 @@ def with_raw_response(self) -> RawCreditAnalysisClient: def news_search( self, *, + q: str, + category: typing.Optional[str] = None, + regions: typing.Optional[str] = None, start_date: typing.Optional[str] = None, end_date: typing.Optional[str] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> CreditAnalysisNewsSearchResponse: """ Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. Parameters ---------- + q : str + Search query for credit news (e.g., 'Tesla bond issuance') + + category : typing.Optional[str] + Filter by news category (e.g., 'bond_issuance', 'rating_change', 'default', 'restructuring') + + regions : typing.Optional[str] + Filter by region (e.g., 'north_america', 'europe', 'asia') + start_date : typing.Optional[str] Start date (YYYY-MM-DD) @@ -45,14 +64,14 @@ def news_search( End date (YYYY-MM-DD) limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 20) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CreditAnalysisNewsSearchResponse Successful response Examples @@ -63,25 +82,39 @@ def news_search( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.credit_analysis.news_search() + client.credit_analysis.news_search( + q="Ares Capital", + limit=10, + ) """ _response = self._raw_client.news_search( - start_date=start_date, end_date=end_date, limit=limit, request_options=request_options + q=q, + category=category, + regions=regions, + start_date=start_date, + end_date=end_date, + limit=limit, + request_options=request_options, ) return _response.data - def news_recent(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def news_recent( + self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> CreditAnalysisNewsRecentResponse: """ Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. Parameters ---------- + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 20) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CreditAnalysisNewsRecentResponse Successful response Examples @@ -94,25 +127,26 @@ def news_recent(self, *, request_options: typing.Optional[RequestOptions] = None ) client.credit_analysis.news_recent() """ - _response = self._raw_client.news_recent(request_options=request_options) + _response = self._raw_client.news_recent(limit=limit, request_options=request_options) return _response.data def news_detail( self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> CreditAnalysisNewsDetailResponse: """ Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. Parameters ---------- news_id : str + News article ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + CreditAnalysisNewsDetailResponse Successful response Examples @@ -130,23 +164,21 @@ def news_detail( _response = self._raw_client.news_detail(news_id, request_options=request_options) return _response.data - def news_attachment( - self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def news_attachment(self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. + **Coming Soon** - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. Parameters ---------- news_id : str + News article ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -168,7 +200,7 @@ def news_bulk( *, queries: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> CreditAnalysisNewsBulkResponse: """ Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. @@ -182,7 +214,7 @@ def news_bulk( Returns ------- - typing.Dict[str, typing.Any] + CreditAnalysisNewsBulkResponse Successful response Examples @@ -200,40 +232,57 @@ def news_bulk( _response = self._raw_client.news_bulk(queries=queries, request_options=request_options) return _response.data - -class AsyncCreditAnalysisClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawCreditAnalysisClient(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawCreditAnalysisClient: + def list_bdcs(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: """ - Retrieves a raw implementation of this client that returns raw responses. + List all tracked Business Development Companies (BDCs). BDCs are publicly traded private credit funds that disclose every loan quarterly in SEC 10-Q/10-K filings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. Returns ------- - AsyncRawCreditAnalysisClient + typing.Dict[str, typing.Any] + List of tracked BDCs + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.list_bdcs() """ - return self._raw_client + _response = self._raw_client.list_bdcs(request_options=request_options) + return _response.data - async def news_search( + def bdc_search( self, *, - start_date: typing.Optional[str] = None, - end_date: typing.Optional[str] = None, + q: str, + seniority: typing.Optional[CreditAnalysisBdcSearchRequestSeniority] = None, + non_accrual_only: typing.Optional[bool] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> typing.Dict[str, typing.Any]: """ - Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. + Search across all indexed BDC portfolios for a borrower, industry, or keyword. + + Searches loan-level data from ~50 BDC quarterly filings covering $150B+ in direct loans. Returns matching investments with terms (spread, seniority, maturity, fair value). Parameters ---------- - start_date : typing.Optional[str] - Start date (YYYY-MM-DD) + q : str + Search query - borrower name, industry, or keyword - end_date : typing.Optional[str] - End date (YYYY-MM-DD) + seniority : typing.Optional[CreditAnalysisBdcSearchRequestSeniority] + Filter by loan seniority + + non_accrual_only : typing.Optional[bool] + Only return defaulted (non-accrual) investments limit : typing.Optional[int] Maximum results @@ -244,118 +293,228 @@ async def news_search( Returns ------- typing.Dict[str, typing.Any] - Successful response + Matching BDC investments Examples -------- - import asyncio - - from runcaptain import AsyncCaptain + from runcaptain import Captain - client = AsyncCaptain( + client = Captain( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) + client.credit_analysis.bdc_search( + q="software", + seniority="first_lien", + limit=10, + ) + """ + _response = self._raw_client.bdc_search( + q=q, seniority=seniority, non_accrual_only=non_accrual_only, limit=limit, request_options=request_options + ) + return _response.data + def bdc_portfolio( + self, + ticker: str, + *, + quarter: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Full Schedule of Investments for a specific BDC, parsed from SEC 10-Q/10-K filings. - async def main() -> None: - await client.credit_analysis.news_search() + Each investment includes: borrower name, industry, investment type, seniority, coupon, spread, reference rate, maturity, principal, fair value, and non-accrual status. + Parameters + ---------- + ticker : str + BDC ticker symbol (e.g., ARCC, BXSL, FSK) - asyncio.run(main()) + quarter : typing.Optional[str] + Quarter like '2025-Q1' - defaults to latest filing + + limit : typing.Optional[int] + Maximum investments to return + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + BDC portfolio holdings + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.bdc_portfolio( + ticker="ARCC", + ) """ - _response = await self._raw_client.news_search( - start_date=start_date, end_date=end_date, limit=limit, request_options=request_options + _response = self._raw_client.bdc_portfolio( + ticker, quarter=quarter, limit=limit, request_options=request_options ) return _response.data - async def news_recent( - self, *, request_options: typing.Optional[RequestOptions] = None + def bdc_stats( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None ) -> typing.Dict[str, typing.Any]: """ - Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. + Aggregate portfolio statistics for a BDC. Returns weighted average spread, non-accrual rate, seniority breakdown, and top industries. Parameters ---------- + ticker : str + BDC ticker symbol + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- typing.Dict[str, typing.Any] - Successful response + BDC aggregate statistics Examples -------- - import asyncio - - from runcaptain import AsyncCaptain + from runcaptain import Captain - client = AsyncCaptain( + client = Captain( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) + client.credit_analysis.bdc_stats( + ticker="ARCC", + ) + """ + _response = self._raw_client.bdc_stats(ticker, request_options=request_options) + return _response.data + def borrower_lookup( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Cross-BDC borrower lookup - find a company across all BDC portfolios. - async def main() -> None: - await client.credit_analysis.news_recent() + Returns every BDC that holds this borrower's debt, with position sizes, spreads, and valuations. Reveals syndication patterns and allows cross-lender credit deterioration monitoring. + Parameters + ---------- + name : str + Borrower/company name - asyncio.run(main()) + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Borrower positions across BDCs + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.borrower_lookup( + name="Finastra", + ) """ - _response = await self._raw_client.news_recent(request_options=request_options) + _response = self._raw_client.borrower_lookup(name, request_options=request_options) return _response.data - async def news_detail( - self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None + def market_overview( + self, *, request_options: typing.Optional[RequestOptions] = None ) -> typing.Dict[str, typing.Any]: """ - Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. + Comprehensive private credit market snapshot from free public sources. + + Returns: + - **Lending standards** (SLOOS): Net % of banks tightening C&I loan standards + - **Credit spreads**: ICE BofA High Yield, BBB, BB, CCC spreads + - **Bank lending**: Total C&I loans outstanding + - **Interest rates**: 10Y Treasury, SOFR + - **Financial conditions**: St. Louis Financial Stress Index, Chicago NFCI + + Data sourced from Federal Reserve (FRED API), updated daily/weekly/quarterly. Parameters ---------- - news_id : str - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- typing.Dict[str, typing.Any] - Successful response + Market data snapshot Examples -------- - import asyncio - - from runcaptain import AsyncCaptain + from runcaptain import Captain - client = AsyncCaptain( + client = Captain( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) + client.credit_analysis.market_overview() + """ + _response = self._raw_client.market_overview(request_options=request_options) + return _response.data + def market_spreads( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Current and historical credit spread data from ICE BofA indices. Returns High Yield, BBB, BB, and CCC spreads with historical trend. - async def main() -> None: - await client.credit_analysis.news_detail( - news_id="abc123def456", - ) + Parameters + ---------- + history : typing.Optional[int] + Number of historical data points + request_options : typing.Optional[RequestOptions] + Request-specific configuration. - asyncio.run(main()) + Returns + ------- + typing.Dict[str, typing.Any] + Credit spread data + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.market_spreads( + history=30, + ) """ - _response = await self._raw_client.news_detail(news_id, request_options=request_options) + _response = self._raw_client.market_spreads(history=history, request_options=request_options) return _response.data - async def news_attachment( - self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None + def lending_standards( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> typing.Dict[str, typing.Any]: """ - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. + Federal Reserve Senior Loan Officer Opinion Survey (SLOOS) data. Shows net % of banks tightening or easing C&I loan standards. Leading indicator for private credit market conditions. Parameters ---------- - news_id : str + history : typing.Optional[int] + Number of quarterly data points request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -363,44 +522,86 @@ async def news_attachment( Returns ------- typing.Dict[str, typing.Any] - Successful response + SLOOS lending standards data Examples -------- - import asyncio - - from runcaptain import AsyncCaptain + from runcaptain import Captain - client = AsyncCaptain( + client = Captain( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) + client.credit_analysis.lending_standards( + history=20, + ) + """ + _response = self._raw_client.lending_standards(history=history, request_options=request_options) + return _response.data + + def funds_search( + self, + *, + q: str, + strategy: typing.Optional[CreditAnalysisFundsSearchRequestStrategy] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search private credit funds via SEC Form D filings. Form D is filed when private funds raise capital under Regulation D. Covers fund formations, managers, and capital raised. + Parameters + ---------- + q : str + Fund name, manager name, or keyword - async def main() -> None: - await client.credit_analysis.news_attachment( - news_id="abc123def456", - ) + strategy : typing.Optional[CreditAnalysisFundsSearchRequestStrategy] + Filter by strategy + + limit : typing.Optional[int] + Maximum results + request_options : typing.Optional[RequestOptions] + Request-specific configuration. - asyncio.run(main()) + Returns + ------- + typing.Dict[str, typing.Any] + Matching funds + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.funds_search( + q="Ares", + strategy="direct_lending", + ) """ - _response = await self._raw_client.news_attachment(news_id, request_options=request_options) + _response = self._raw_client.funds_search(q=q, strategy=strategy, limit=limit, request_options=request_options) return _response.data - async def news_bulk( + def fund_formations( self, *, - queries: typing.Optional[typing.Sequence[str]] = OMIT, + days_back: typing.Optional[int] = None, + limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> typing.Dict[str, typing.Any]: """ - Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. + Recent private credit fund formations from SEC Form D filings. Shows new funds launching in the private credit space. Parameters ---------- - queries : typing.Optional[typing.Sequence[str]] - List of search queries + days_back : typing.Optional[int] + Look back period in days + + limit : typing.Optional[int] + Maximum results request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -408,27 +609,1687 @@ async def news_bulk( Returns ------- typing.Dict[str, typing.Any] - Successful response + Recent fund formations Examples -------- - import asyncio - - from runcaptain import AsyncCaptain + from runcaptain import Captain - client = AsyncCaptain( + client = Captain( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) + client.credit_analysis.fund_formations( + days_back=90, + ) + """ + _response = self._raw_client.fund_formations(days_back=days_back, limit=limit, request_options=request_options) + return _response.data + def list_managers(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + """ + List known private credit fund managers with their strategies and SEC CIK numbers. - async def main() -> None: - await client.credit_analysis.news_bulk( - queries=["Apple credit rating", "Tesla debt analysis"], - ) + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + Returns + ------- + typing.Dict[str, typing.Any] + List of credit managers - asyncio.run(main()) - """ - _response = await self._raw_client.news_bulk(queries=queries, request_options=request_options) + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.list_managers() + """ + _response = self._raw_client.list_managers(request_options=request_options) + return _response.data + + def manager_detail( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Detailed information about a private credit fund manager. Combines SEC filing data with known manager intelligence. Returns filing history, fund count, strategy, and recent Form D filings. + + Parameters + ---------- + name : str + Manager name (e.g., 'Ares Management') + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Manager details + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.manager_detail( + name="Ares Management", + ) + """ + _response = self._raw_client.manager_detail(name, request_options=request_options) + return _response.data + + def sba_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + state: typing.Optional[str] = None, + naics: typing.Optional[str] = None, + status: typing.Optional[CreditAnalysisSbaSearchRequestStatus] = None, + min_amount: typing.Optional[float] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search SBA 7(a) loan data. Contains loan-level data with borrower, lender, terms, and performance for 100,000+ loans (FY2020-present). + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower name (partial match) + + lender : typing.Optional[str] + Bank/lender name (partial match) + + state : typing.Optional[str] + State code (e.g., CA, NY, TX) + + naics : typing.Optional[str] + NAICS code prefix (e.g., 5112 for software) + + status : typing.Optional[CreditAnalysisSbaSearchRequestStatus] + Loan status: CHGOFF (charged off), PIF (paid in full) + + min_amount : typing.Optional[float] + Minimum gross approval amount ($) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching SBA loans + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.sba_search( + state="CA", + status="CHGOFF", + limit=5, + ) + """ + _response = self._raw_client.sba_search( + borrower=borrower, + lender=lender, + state=state, + naics=naics, + status=status, + min_amount=min_amount, + limit=limit, + request_options=request_options, + ) + return _response.data + + def sba_stats(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + """ + Aggregate SBA 7(a) loan statistics. Returns default rates, average loan size, top states, and top industries from 100,000+ indexed loans. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + SBA aggregate statistics + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.sba_stats() + """ + _response = self._raw_client.sba_stats(request_options=request_options) + return _response.data + + def agreements_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + date_from: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search SEC 8-K filings for credit agreements. When public companies enter material credit facilities, they file the agreement as an exhibit to an 8-K. + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower/company name + + lender : typing.Optional[str] + Lender/agent name + + date_from : typing.Optional[str] + Start date (YYYY-MM-DD) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching credit agreement filings + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.agreements_search( + borrower="Dell", + ) + """ + _response = self._raw_client.agreements_search( + borrower=borrower, lender=lender, date_from=date_from, limit=limit, request_options=request_options + ) + return _response.data + + def agreement_detail( + self, company: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Get the most recent credit agreement filing for a company. Locates the 8-K filing containing the credit agreement. + + Parameters + ---------- + company : str + Company name + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Credit agreement detail + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.agreement_detail( + company="Alloy", + ) + """ + _response = self._raw_client.agreement_detail(company, request_options=request_options) + return _response.data + + def nport_funds(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + """ + List tracked private credit interval funds that file N-PORT. These filings contain loan-by-loan holdings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + List of tracked funds + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.nport_funds() + """ + _response = self._raw_client.nport_funds(request_options=request_options) + return _response.data + + def nport_search( + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Search N-PORT filings for private credit holdings matching a borrower name or keyword. + + Parameters + ---------- + q : str + Borrower name or keyword + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching N-PORT holdings + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.nport_search( + q="Brightstone Capital", + limit=10, + ) + """ + _response = self._raw_client.nport_search(q=q, limit=limit, request_options=request_options) + return _response.data + + def nport_fund_filings( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Get N-PORT filing list for a specific private credit interval fund. + + Parameters + ---------- + ticker : str + Fund ticker (e.g., CCLFX, AFT) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Fund N-PORT filings + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.nport_fund_filings( + ticker="CCLFX", + ) + """ + _response = self._raw_client.nport_fund_filings(ticker, request_options=request_options) + return _response.data + + def relationships_search( + self, + *, + lender: typing.Optional[str] = None, + borrower: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search lender-borrower relationships across BDC portfolios. Provide either `lender` (to find their borrowers) or `borrower` (to find their lenders). + + Parameters + ---------- + lender : typing.Optional[str] + Lender name - find their borrowers + + borrower : typing.Optional[str] + Borrower name - find their lenders + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching relationships + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.relationships_search( + lender="Ares Capital", + ) + """ + _response = self._raw_client.relationships_search( + lender=lender, borrower=borrower, request_options=request_options + ) + return _response.data + + def ucc_portals( + self, *, state: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Get UCC filing search portal information for free state-level databases. Returns URLs for CA, NY, TX, FL, IL portals and a list of known major private credit lenders. + + Parameters + ---------- + state : typing.Optional[str] + State code (e.g., CA, NY). Omit for all states. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + UCC portal information + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.credit_analysis.ucc_portals() + """ + _response = self._raw_client.ucc_portals(state=state, request_options=request_options) + return _response.data + + +class AsyncCreditAnalysisClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCreditAnalysisClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCreditAnalysisClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCreditAnalysisClient + """ + return self._raw_client + + async def news_search( + self, + *, + q: str, + category: typing.Optional[str] = None, + regions: typing.Optional[str] = None, + start_date: typing.Optional[str] = None, + end_date: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreditAnalysisNewsSearchResponse: + """ + Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. + + Parameters + ---------- + q : str + Search query for credit news (e.g., 'Tesla bond issuance') + + category : typing.Optional[str] + Filter by news category (e.g., 'bond_issuance', 'rating_change', 'default', 'restructuring') + + regions : typing.Optional[str] + Filter by region (e.g., 'north_america', 'europe', 'asia') + + start_date : typing.Optional[str] + Start date (YYYY-MM-DD) + + end_date : typing.Optional[str] + End date (YYYY-MM-DD) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 20) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreditAnalysisNewsSearchResponse + Successful response + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.news_search( + q="Ares Capital", + limit=10, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.news_search( + q=q, + category=category, + regions=regions, + start_date=start_date, + end_date=end_date, + limit=limit, + request_options=request_options, + ) + return _response.data + + async def news_recent( + self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> CreditAnalysisNewsRecentResponse: + """ + Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. + + Parameters + ---------- + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 20) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreditAnalysisNewsRecentResponse + Successful response + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.news_recent() + + + asyncio.run(main()) + """ + _response = await self._raw_client.news_recent(limit=limit, request_options=request_options) + return _response.data + + async def news_detail( + self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> CreditAnalysisNewsDetailResponse: + """ + Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. + + Parameters + ---------- + news_id : str + News article ID + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreditAnalysisNewsDetailResponse + Successful response + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.news_detail( + news_id="abc123def456", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.news_detail(news_id, request_options=request_options) + return _response.data + + async def news_attachment(self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + **Coming Soon** - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. + + Parameters + ---------- + news_id : str + News article ID + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.news_attachment( + news_id="abc123def456", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.news_attachment(news_id, request_options=request_options) + return _response.data + + async def news_bulk( + self, + *, + queries: typing.Optional[typing.Sequence[str]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreditAnalysisNewsBulkResponse: + """ + Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. + + Parameters + ---------- + queries : typing.Optional[typing.Sequence[str]] + List of search queries + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreditAnalysisNewsBulkResponse + Successful response + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.news_bulk( + queries=["Apple credit rating", "Tesla debt analysis"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.news_bulk(queries=queries, request_options=request_options) + return _response.data + + async def list_bdcs( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + List all tracked Business Development Companies (BDCs). BDCs are publicly traded private credit funds that disclose every loan quarterly in SEC 10-Q/10-K filings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + List of tracked BDCs + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.list_bdcs() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_bdcs(request_options=request_options) + return _response.data + + async def bdc_search( + self, + *, + q: str, + seniority: typing.Optional[CreditAnalysisBdcSearchRequestSeniority] = None, + non_accrual_only: typing.Optional[bool] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search across all indexed BDC portfolios for a borrower, industry, or keyword. + + Searches loan-level data from ~50 BDC quarterly filings covering $150B+ in direct loans. Returns matching investments with terms (spread, seniority, maturity, fair value). + + Parameters + ---------- + q : str + Search query - borrower name, industry, or keyword + + seniority : typing.Optional[CreditAnalysisBdcSearchRequestSeniority] + Filter by loan seniority + + non_accrual_only : typing.Optional[bool] + Only return defaulted (non-accrual) investments + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching BDC investments + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.bdc_search( + q="software", + seniority="first_lien", + limit=10, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.bdc_search( + q=q, seniority=seniority, non_accrual_only=non_accrual_only, limit=limit, request_options=request_options + ) + return _response.data + + async def bdc_portfolio( + self, + ticker: str, + *, + quarter: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Full Schedule of Investments for a specific BDC, parsed from SEC 10-Q/10-K filings. + + Each investment includes: borrower name, industry, investment type, seniority, coupon, spread, reference rate, maturity, principal, fair value, and non-accrual status. + + Parameters + ---------- + ticker : str + BDC ticker symbol (e.g., ARCC, BXSL, FSK) + + quarter : typing.Optional[str] + Quarter like '2025-Q1' - defaults to latest filing + + limit : typing.Optional[int] + Maximum investments to return + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + BDC portfolio holdings + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.bdc_portfolio( + ticker="ARCC", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.bdc_portfolio( + ticker, quarter=quarter, limit=limit, request_options=request_options + ) + return _response.data + + async def bdc_stats( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Aggregate portfolio statistics for a BDC. Returns weighted average spread, non-accrual rate, seniority breakdown, and top industries. + + Parameters + ---------- + ticker : str + BDC ticker symbol + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + BDC aggregate statistics + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.bdc_stats( + ticker="ARCC", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.bdc_stats(ticker, request_options=request_options) + return _response.data + + async def borrower_lookup( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Cross-BDC borrower lookup - find a company across all BDC portfolios. + + Returns every BDC that holds this borrower's debt, with position sizes, spreads, and valuations. Reveals syndication patterns and allows cross-lender credit deterioration monitoring. + + Parameters + ---------- + name : str + Borrower/company name + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Borrower positions across BDCs + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.borrower_lookup( + name="Finastra", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.borrower_lookup(name, request_options=request_options) + return _response.data + + async def market_overview( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Comprehensive private credit market snapshot from free public sources. + + Returns: + - **Lending standards** (SLOOS): Net % of banks tightening C&I loan standards + - **Credit spreads**: ICE BofA High Yield, BBB, BB, CCC spreads + - **Bank lending**: Total C&I loans outstanding + - **Interest rates**: 10Y Treasury, SOFR + - **Financial conditions**: St. Louis Financial Stress Index, Chicago NFCI + + Data sourced from Federal Reserve (FRED API), updated daily/weekly/quarterly. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Market data snapshot + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.market_overview() + + + asyncio.run(main()) + """ + _response = await self._raw_client.market_overview(request_options=request_options) + return _response.data + + async def market_spreads( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Current and historical credit spread data from ICE BofA indices. Returns High Yield, BBB, BB, and CCC spreads with historical trend. + + Parameters + ---------- + history : typing.Optional[int] + Number of historical data points + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Credit spread data + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.market_spreads( + history=30, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.market_spreads(history=history, request_options=request_options) + return _response.data + + async def lending_standards( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Federal Reserve Senior Loan Officer Opinion Survey (SLOOS) data. Shows net % of banks tightening or easing C&I loan standards. Leading indicator for private credit market conditions. + + Parameters + ---------- + history : typing.Optional[int] + Number of quarterly data points + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + SLOOS lending standards data + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.lending_standards( + history=20, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.lending_standards(history=history, request_options=request_options) + return _response.data + + async def funds_search( + self, + *, + q: str, + strategy: typing.Optional[CreditAnalysisFundsSearchRequestStrategy] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search private credit funds via SEC Form D filings. Form D is filed when private funds raise capital under Regulation D. Covers fund formations, managers, and capital raised. + + Parameters + ---------- + q : str + Fund name, manager name, or keyword + + strategy : typing.Optional[CreditAnalysisFundsSearchRequestStrategy] + Filter by strategy + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching funds + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.funds_search( + q="Ares", + strategy="direct_lending", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.funds_search( + q=q, strategy=strategy, limit=limit, request_options=request_options + ) + return _response.data + + async def fund_formations( + self, + *, + days_back: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Recent private credit fund formations from SEC Form D filings. Shows new funds launching in the private credit space. + + Parameters + ---------- + days_back : typing.Optional[int] + Look back period in days + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Recent fund formations + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.fund_formations( + days_back=90, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.fund_formations( + days_back=days_back, limit=limit, request_options=request_options + ) + return _response.data + + async def list_managers( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + List known private credit fund managers with their strategies and SEC CIK numbers. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + List of credit managers + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.list_managers() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_managers(request_options=request_options) + return _response.data + + async def manager_detail( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Detailed information about a private credit fund manager. Combines SEC filing data with known manager intelligence. Returns filing history, fund count, strategy, and recent Form D filings. + + Parameters + ---------- + name : str + Manager name (e.g., 'Ares Management') + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Manager details + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.manager_detail( + name="Ares Management", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.manager_detail(name, request_options=request_options) + return _response.data + + async def sba_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + state: typing.Optional[str] = None, + naics: typing.Optional[str] = None, + status: typing.Optional[CreditAnalysisSbaSearchRequestStatus] = None, + min_amount: typing.Optional[float] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search SBA 7(a) loan data. Contains loan-level data with borrower, lender, terms, and performance for 100,000+ loans (FY2020-present). + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower name (partial match) + + lender : typing.Optional[str] + Bank/lender name (partial match) + + state : typing.Optional[str] + State code (e.g., CA, NY, TX) + + naics : typing.Optional[str] + NAICS code prefix (e.g., 5112 for software) + + status : typing.Optional[CreditAnalysisSbaSearchRequestStatus] + Loan status: CHGOFF (charged off), PIF (paid in full) + + min_amount : typing.Optional[float] + Minimum gross approval amount ($) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching SBA loans + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.sba_search( + state="CA", + status="CHGOFF", + limit=5, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.sba_search( + borrower=borrower, + lender=lender, + state=state, + naics=naics, + status=status, + min_amount=min_amount, + limit=limit, + request_options=request_options, + ) + return _response.data + + async def sba_stats( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Aggregate SBA 7(a) loan statistics. Returns default rates, average loan size, top states, and top industries from 100,000+ indexed loans. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + SBA aggregate statistics + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.sba_stats() + + + asyncio.run(main()) + """ + _response = await self._raw_client.sba_stats(request_options=request_options) + return _response.data + + async def agreements_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + date_from: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search SEC 8-K filings for credit agreements. When public companies enter material credit facilities, they file the agreement as an exhibit to an 8-K. + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower/company name + + lender : typing.Optional[str] + Lender/agent name + + date_from : typing.Optional[str] + Start date (YYYY-MM-DD) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching credit agreement filings + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.agreements_search( + borrower="Dell", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.agreements_search( + borrower=borrower, lender=lender, date_from=date_from, limit=limit, request_options=request_options + ) + return _response.data + + async def agreement_detail( + self, company: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Get the most recent credit agreement filing for a company. Locates the 8-K filing containing the credit agreement. + + Parameters + ---------- + company : str + Company name + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Credit agreement detail + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.agreement_detail( + company="Alloy", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.agreement_detail(company, request_options=request_options) + return _response.data + + async def nport_funds( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + List tracked private credit interval funds that file N-PORT. These filings contain loan-by-loan holdings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + List of tracked funds + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.nport_funds() + + + asyncio.run(main()) + """ + _response = await self._raw_client.nport_funds(request_options=request_options) + return _response.data + + async def nport_search( + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Search N-PORT filings for private credit holdings matching a borrower name or keyword. + + Parameters + ---------- + q : str + Borrower name or keyword + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching N-PORT holdings + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.nport_search( + q="Brightstone Capital", + limit=10, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.nport_search(q=q, limit=limit, request_options=request_options) + return _response.data + + async def nport_fund_filings( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Get N-PORT filing list for a specific private credit interval fund. + + Parameters + ---------- + ticker : str + Fund ticker (e.g., CCLFX, AFT) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Fund N-PORT filings + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.nport_fund_filings( + ticker="CCLFX", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.nport_fund_filings(ticker, request_options=request_options) + return _response.data + + async def relationships_search( + self, + *, + lender: typing.Optional[str] = None, + borrower: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Dict[str, typing.Any]: + """ + Search lender-borrower relationships across BDC portfolios. Provide either `lender` (to find their borrowers) or `borrower` (to find their lenders). + + Parameters + ---------- + lender : typing.Optional[str] + Lender name - find their borrowers + + borrower : typing.Optional[str] + Borrower name - find their lenders + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + Matching relationships + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.relationships_search( + lender="Ares Capital", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.relationships_search( + lender=lender, borrower=borrower, request_options=request_options + ) + return _response.data + + async def ucc_portals( + self, *, state: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Any]: + """ + Get UCC filing search portal information for free state-level databases. Returns URLs for CA, NY, TX, FL, IL portals and a list of known major private credit lenders. + + Parameters + ---------- + state : typing.Optional[str] + State code (e.g., CA, NY). Omit for all states. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Any] + UCC portal information + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.credit_analysis.ucc_portals() + + + asyncio.run(main()) + """ + _response = await self._raw_client.ucc_portals(state=state, request_options=request_options) return _response.data diff --git a/src/runcaptain/credit_analysis/raw_client.py b/src/runcaptain/credit_analysis/raw_client.py index 176b52d..a8631e9 100644 --- a/src/runcaptain/credit_analysis/raw_client.py +++ b/src/runcaptain/credit_analysis/raw_client.py @@ -7,11 +7,20 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.credit_analysis_bdc_search_request_seniority import CreditAnalysisBdcSearchRequestSeniority +from .types.credit_analysis_funds_search_request_strategy import CreditAnalysisFundsSearchRequestStrategy +from .types.credit_analysis_news_bulk_response import CreditAnalysisNewsBulkResponse +from .types.credit_analysis_news_detail_response import CreditAnalysisNewsDetailResponse +from .types.credit_analysis_news_recent_response import CreditAnalysisNewsRecentResponse +from .types.credit_analysis_news_search_response import CreditAnalysisNewsSearchResponse +from .types.credit_analysis_sba_search_request_status import CreditAnalysisSbaSearchRequestStatus +from pydantic import ValidationError # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -24,16 +33,28 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def news_search( self, *, + q: str, + category: typing.Optional[str] = None, + regions: typing.Optional[str] = None, start_date: typing.Optional[str] = None, end_date: typing.Optional[str] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CreditAnalysisNewsSearchResponse]: """ Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. Parameters ---------- + q : str + Search query for credit news (e.g., 'Tesla bond issuance') + + category : typing.Optional[str] + Filter by news category (e.g., 'bond_issuance', 'rating_change', 'default', 'restructuring') + + regions : typing.Optional[str] + Filter by region (e.g., 'north_america', 'europe', 'asia') + start_date : typing.Optional[str] Start date (YYYY-MM-DD) @@ -41,20 +62,23 @@ def news_search( End date (YYYY-MM-DD) limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 20) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CreditAnalysisNewsSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/credit-analysis/news/search", method="GET", params={ + "q": q, + "category": category, + "regions": regions, "start_date": start_date, "end_date": end_date, "limit": limit, @@ -64,9 +88,9 @@ def news_search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CreditAnalysisNewsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CreditAnalysisNewsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -85,35 +109,45 @@ def news_search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def news_recent( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CreditAnalysisNewsRecentResponse]: """ Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. Parameters ---------- + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 20) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CreditAnalysisNewsRecentResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/credit-analysis/news/recent", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CreditAnalysisNewsRecentResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CreditAnalysisNewsRecentResponse, # type: ignore object_=_response.json(), ), ) @@ -132,24 +166,29 @@ def news_recent( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def news_detail( self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CreditAnalysisNewsDetailResponse]: """ Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. Parameters ---------- news_id : str + News article ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CreditAnalysisNewsDetailResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -160,9 +199,9 @@ def news_detail( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CreditAnalysisNewsDetailResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CreditAnalysisNewsDetailResponse, # type: ignore object_=_response.json(), ), ) @@ -192,25 +231,29 @@ def news_detail( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def news_attachment( self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. + **Coming Soon** - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. Parameters ---------- news_id : str + News article ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/credit-analysis/news/{jsonable_encoder(news_id)}/attachment", @@ -219,14 +262,7 @@ def news_attachment( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -263,6 +299,10 @@ def news_attachment( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def news_bulk( @@ -270,7 +310,7 @@ def news_bulk( *, queries: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[CreditAnalysisNewsBulkResponse]: """ Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. @@ -284,7 +324,7 @@ def news_bulk( Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[CreditAnalysisNewsBulkResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -302,9 +342,9 @@ def news_bulk( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + CreditAnalysisNewsBulkResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=CreditAnalysisNewsBulkResponse, # type: ignore object_=_response.json(), ), ) @@ -323,31 +363,76 @@ def news_bulk( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def list_bdcs( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + List all tracked Business Development Companies (BDCs). BDCs are publicly traded private credit funds that disclose every loan quarterly in SEC 10-Q/10-K filings. -class AsyncRawCreditAnalysisClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. - async def news_search( + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + List of tracked BDCs + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/bdc", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def bdc_search( self, *, - start_date: typing.Optional[str] = None, - end_date: typing.Optional[str] = None, + q: str, + seniority: typing.Optional[CreditAnalysisBdcSearchRequestSeniority] = None, + non_accrual_only: typing.Optional[bool] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[typing.Dict[str, typing.Any]]: """ - Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. + Search across all indexed BDC portfolios for a borrower, industry, or keyword. + + Searches loan-level data from ~50 BDC quarterly filings covering $150B+ in direct loans. Returns matching investments with terms (spread, seniority, maturity, fair value). Parameters ---------- - start_date : typing.Optional[str] - Start date (YYYY-MM-DD) + q : str + Search query - borrower name, industry, or keyword - end_date : typing.Optional[str] - End date (YYYY-MM-DD) + seniority : typing.Optional[CreditAnalysisBdcSearchRequestSeniority] + Filter by loan seniority + + non_accrual_only : typing.Optional[bool] + Only return defaulted (non-accrual) investments limit : typing.Optional[int] Maximum results @@ -357,15 +442,16 @@ async def news_search( Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[typing.Dict[str, typing.Any]] + Matching BDC investments """ - _response = await self._client_wrapper.httpx_client.request( - "v2/datasets/odyssey/credit-analysis/news/search", + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/bdc/search", method="GET", params={ - "start_date": start_date, - "end_date": end_date, + "q": q, + "seniority": seniority, + "non_accrual_only": non_accrual_only, "limit": limit, }, request_options=request_options, @@ -379,42 +465,55 @@ async def news_search( object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) + return HttpResponse(response=_response, data=_data) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def news_recent( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + def bdc_portfolio( + self, + ticker: str, + *, + quarter: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.Dict[str, typing.Any]]: """ - Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. + Full Schedule of Investments for a specific BDC, parsed from SEC 10-Q/10-K filings. + + Each investment includes: borrower name, industry, investment type, seniority, coupon, spread, reference rate, maturity, principal, fair value, and non-accrual status. Parameters ---------- + ticker : str + BDC ticker symbol (e.g., ARCC, BXSL, FSK) + + quarter : typing.Optional[str] + Quarter like '2025-Q1' - defaults to latest filing + + limit : typing.Optional[int] + Maximum investments to return + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[typing.Dict[str, typing.Any]] + BDC portfolio holdings """ - _response = await self._client_wrapper.httpx_client.request( - "v2/datasets/odyssey/credit-analysis/news/recent", + _response = self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/bdc/{jsonable_encoder(ticker)}/portfolio", method="GET", + params={ + "quarter": quarter, + "limit": limit, + }, request_options=request_options, ) try: @@ -426,43 +525,37 @@ async def news_recent( object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) + return HttpResponse(response=_response, data=_data) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def news_detail( - self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + def bdc_stats( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: """ - Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. + Aggregate portfolio statistics for a BDC. Returns weighted average spread, non-accrual rate, seniority breakdown, and top industries. Parameters ---------- - news_id : str + ticker : str + BDC ticker symbol request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[typing.Dict[str, typing.Any]] + BDC aggregate statistics """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/credit-analysis/news/{jsonable_encoder(news_id)}", + _response = self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/bdc/{jsonable_encoder(ticker)}/stats", method="GET", request_options=request_options, ) @@ -475,54 +568,39 @@ async def news_detail( object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) + return HttpResponse(response=_response, data=_data) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def news_attachment( - self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + def borrower_lookup( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: """ - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. + Cross-BDC borrower lookup - find a company across all BDC portfolios. + + Returns every BDC that holds this borrower's debt, with position sizes, spreads, and valuations. Reveals syndication patterns and allows cross-lender credit deterioration monitoring. Parameters ---------- - news_id : str + name : str + Borrower/company name request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[typing.Dict[str, typing.Any]] + Borrower positions across BDCs """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/credit-analysis/news/{jsonable_encoder(news_id)}/attachment", + _response = self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/borrowers/{jsonable_encoder(name)}", method="GET", request_options=request_options, ) @@ -535,78 +613,45 @@ async def news_attachment( object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 501: - raise NotImplementedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) + return HttpResponse(response=_response, data=_data) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def news_bulk( - self, - *, - queries: typing.Optional[typing.Sequence[str]] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + def market_overview( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: """ - Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. + Comprehensive private credit market snapshot from free public sources. + + Returns: + - **Lending standards** (SLOOS): Net % of banks tightening C&I loan standards + - **Credit spreads**: ICE BofA High Yield, BBB, BB, CCC spreads + - **Bank lending**: Total C&I loans outstanding + - **Interest rates**: 10Y Treasury, SOFR + - **Financial conditions**: St. Louis Financial Stress Index, Chicago NFCI + + Data sourced from Federal Reserve (FRED API), updated daily/weekly/quarterly. Parameters ---------- - queries : typing.Optional[typing.Sequence[str]] - List of search queries - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[typing.Dict[str, typing.Any]] + Market data snapshot """ - _response = await self._client_wrapper.httpx_client.request( - "v2/datasets/odyssey/credit-analysis/news/bulk", - method="POST", - json={ - "queries": queries, - }, - headers={ - "content-type": "application/json", - }, + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/market/overview", + method="GET", request_options=request_options, - omit=OMIT, ) try: if 200 <= _response.status_code < 300: @@ -617,11 +662,1091 @@ async def news_bulk( object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def market_spreads( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Current and historical credit spread data from ICE BofA indices. Returns High Yield, BBB, BB, and CCC spreads with historical trend. + + Parameters + ---------- + history : typing.Optional[int] + Number of historical data points + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Credit spread data + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/market/spreads", + method="GET", + params={ + "history": history, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def lending_standards( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Federal Reserve Senior Loan Officer Opinion Survey (SLOOS) data. Shows net % of banks tightening or easing C&I loan standards. Leading indicator for private credit market conditions. + + Parameters + ---------- + history : typing.Optional[int] + Number of quarterly data points + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + SLOOS lending standards data + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/market/lending-standards", + method="GET", + params={ + "history": history, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def funds_search( + self, + *, + q: str, + strategy: typing.Optional[CreditAnalysisFundsSearchRequestStrategy] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Search private credit funds via SEC Form D filings. Form D is filed when private funds raise capital under Regulation D. Covers fund formations, managers, and capital raised. + + Parameters + ---------- + q : str + Fund name, manager name, or keyword + + strategy : typing.Optional[CreditAnalysisFundsSearchRequestStrategy] + Filter by strategy + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Matching funds + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/funds/search", + method="GET", + params={ + "q": q, + "strategy": strategy, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def fund_formations( + self, + *, + days_back: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Recent private credit fund formations from SEC Form D filings. Shows new funds launching in the private credit space. + + Parameters + ---------- + days_back : typing.Optional[int] + Look back period in days + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Recent fund formations + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/funds/formations", + method="GET", + params={ + "days_back": days_back, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list_managers( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + List known private credit fund managers with their strategies and SEC CIK numbers. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + List of credit managers + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/funds/managers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def manager_detail( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Detailed information about a private credit fund manager. Combines SEC filing data with known manager intelligence. Returns filing history, fund count, strategy, and recent Form D filings. + + Parameters + ---------- + name : str + Manager name (e.g., 'Ares Management') + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Manager details + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/funds/managers/{jsonable_encoder(name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def sba_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + state: typing.Optional[str] = None, + naics: typing.Optional[str] = None, + status: typing.Optional[CreditAnalysisSbaSearchRequestStatus] = None, + min_amount: typing.Optional[float] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Search SBA 7(a) loan data. Contains loan-level data with borrower, lender, terms, and performance for 100,000+ loans (FY2020-present). + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower name (partial match) + + lender : typing.Optional[str] + Bank/lender name (partial match) + + state : typing.Optional[str] + State code (e.g., CA, NY, TX) + + naics : typing.Optional[str] + NAICS code prefix (e.g., 5112 for software) + + status : typing.Optional[CreditAnalysisSbaSearchRequestStatus] + Loan status: CHGOFF (charged off), PIF (paid in full) + + min_amount : typing.Optional[float] + Minimum gross approval amount ($) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Matching SBA loans + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/sba/search", + method="GET", + params={ + "borrower": borrower, + "lender": lender, + "state": state, + "naics": naics, + "status": status, + "min_amount": min_amount, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def sba_stats( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Aggregate SBA 7(a) loan statistics. Returns default rates, average loan size, top states, and top industries from 100,000+ indexed loans. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + SBA aggregate statistics + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/sba/stats", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def agreements_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + date_from: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Search SEC 8-K filings for credit agreements. When public companies enter material credit facilities, they file the agreement as an exhibit to an 8-K. + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower/company name + + lender : typing.Optional[str] + Lender/agent name + + date_from : typing.Optional[str] + Start date (YYYY-MM-DD) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Matching credit agreement filings + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/agreements/search", + method="GET", + params={ + "borrower": borrower, + "lender": lender, + "date_from": date_from, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def agreement_detail( + self, company: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Get the most recent credit agreement filing for a company. Locates the 8-K filing containing the credit agreement. + + Parameters + ---------- + company : str + Company name + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Credit agreement detail + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/agreements/{jsonable_encoder(company)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def nport_funds( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + List tracked private credit interval funds that file N-PORT. These filings contain loan-by-loan holdings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + List of tracked funds + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/nport/funds", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def nport_search( + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Search N-PORT filings for private credit holdings matching a borrower name or keyword. + + Parameters + ---------- + q : str + Borrower name or keyword + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Matching N-PORT holdings + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/nport/search", + method="GET", + params={ + "q": q, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def nport_fund_filings( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Get N-PORT filing list for a specific private credit interval fund. + + Parameters + ---------- + ticker : str + Fund ticker (e.g., CCLFX, AFT) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Fund N-PORT filings + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/nport/{jsonable_encoder(ticker)}/filings", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def relationships_search( + self, + *, + lender: typing.Optional[str] = None, + borrower: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Search lender-borrower relationships across BDC portfolios. Provide either `lender` (to find their borrowers) or `borrower` (to find their lenders). + + Parameters + ---------- + lender : typing.Optional[str] + Lender name - find their borrowers + + borrower : typing.Optional[str] + Borrower name - find their lenders + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + Matching relationships + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/relationships/search", + method="GET", + params={ + "lender": lender, + "borrower": borrower, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def ucc_portals( + self, *, state: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Any]]: + """ + Get UCC filing search portal information for free state-level databases. Returns URLs for CA, NY, TX, FL, IL portals and a list of known major private credit lenders. + + Parameters + ---------- + state : typing.Optional[str] + State code (e.g., CA, NY). Omit for all states. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Any]] + UCC portal information + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/relationships/portals", + method="GET", + params={ + "state": state, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCreditAnalysisClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def news_search( + self, + *, + q: str, + category: typing.Optional[str] = None, + regions: typing.Optional[str] = None, + start_date: typing.Optional[str] = None, + end_date: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreditAnalysisNewsSearchResponse]: + """ + Search for credit-related news and filings including bond issuances, credit rating changes, and default events. Returns matching news with dates, sources, and content. + + Parameters + ---------- + q : str + Search query for credit news (e.g., 'Tesla bond issuance') + + category : typing.Optional[str] + Filter by news category (e.g., 'bond_issuance', 'rating_change', 'default', 'restructuring') + + regions : typing.Optional[str] + Filter by region (e.g., 'north_america', 'europe', 'asia') + + start_date : typing.Optional[str] + Start date (YYYY-MM-DD) + + end_date : typing.Optional[str] + End date (YYYY-MM-DD) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 20) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreditAnalysisNewsSearchResponse] + Successful response + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/news/search", + method="GET", + params={ + "q": q, + "category": category, + "regions": regions, + "start_date": start_date, + "end_date": end_date, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreditAnalysisNewsSearchResponse, + parse_obj_as( + type_=CreditAnalysisNewsSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def news_recent( + self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CreditAnalysisNewsRecentResponse]: + """ + Get most recent credit news and filings. Returns latest credit events, ratings, and bond issuances from the past 30 days. + + Parameters + ---------- + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 20) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreditAnalysisNewsRecentResponse] + Successful response + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/news/recent", + method="GET", + params={ + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreditAnalysisNewsRecentResponse, + parse_obj_as( + type_=CreditAnalysisNewsRecentResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def news_detail( + self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CreditAnalysisNewsDetailResponse]: + """ + Get detailed credit news article or filing including full text, metadata, and related entities. Returns comprehensive news item with analysis. + + Parameters + ---------- + news_id : str + News article ID + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreditAnalysisNewsDetailResponse] + Successful response + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/news/{jsonable_encoder(news_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreditAnalysisNewsDetailResponse, + parse_obj_as( + type_=CreditAnalysisNewsDetailResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def news_attachment( + self, news_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[None]: + """ + **Coming Soon** - Download attachment files associated with credit news including prospectuses, indentures, and rating reports. Returns document files. + + Parameters + ---------- + news_id : str + News article ID + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/news/{jsonable_encoder(news_id)}/attachment", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 501: + raise NotImplementedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def news_bulk( + self, + *, + queries: typing.Optional[typing.Sequence[str]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreditAnalysisNewsBulkResponse]: + """ + Retrieve multiple credit news articles by ID in a single request. Returns batch results with article details for each requested ID. + + Parameters + ---------- + queries : typing.Optional[typing.Sequence[str]] + List of search queries + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreditAnalysisNewsBulkResponse] + Successful response + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/news/bulk", + method="POST", + json={ + "queries": queries, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreditAnalysisNewsBulkResponse, + parse_obj_as( + type_=CreditAnalysisNewsBulkResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( typing.Any, parse_obj_as( type_=typing.Any, # type: ignore @@ -632,4 +1757,1058 @@ async def news_bulk( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_bdcs( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + List all tracked Business Development Companies (BDCs). BDCs are publicly traded private credit funds that disclose every loan quarterly in SEC 10-Q/10-K filings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + List of tracked BDCs + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/bdc", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def bdc_search( + self, + *, + q: str, + seniority: typing.Optional[CreditAnalysisBdcSearchRequestSeniority] = None, + non_accrual_only: typing.Optional[bool] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Search across all indexed BDC portfolios for a borrower, industry, or keyword. + + Searches loan-level data from ~50 BDC quarterly filings covering $150B+ in direct loans. Returns matching investments with terms (spread, seniority, maturity, fair value). + + Parameters + ---------- + q : str + Search query - borrower name, industry, or keyword + + seniority : typing.Optional[CreditAnalysisBdcSearchRequestSeniority] + Filter by loan seniority + + non_accrual_only : typing.Optional[bool] + Only return defaulted (non-accrual) investments + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Matching BDC investments + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/bdc/search", + method="GET", + params={ + "q": q, + "seniority": seniority, + "non_accrual_only": non_accrual_only, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def bdc_portfolio( + self, + ticker: str, + *, + quarter: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Full Schedule of Investments for a specific BDC, parsed from SEC 10-Q/10-K filings. + + Each investment includes: borrower name, industry, investment type, seniority, coupon, spread, reference rate, maturity, principal, fair value, and non-accrual status. + + Parameters + ---------- + ticker : str + BDC ticker symbol (e.g., ARCC, BXSL, FSK) + + quarter : typing.Optional[str] + Quarter like '2025-Q1' - defaults to latest filing + + limit : typing.Optional[int] + Maximum investments to return + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + BDC portfolio holdings + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/bdc/{jsonable_encoder(ticker)}/portfolio", + method="GET", + params={ + "quarter": quarter, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def bdc_stats( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Aggregate portfolio statistics for a BDC. Returns weighted average spread, non-accrual rate, seniority breakdown, and top industries. + + Parameters + ---------- + ticker : str + BDC ticker symbol + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + BDC aggregate statistics + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/bdc/{jsonable_encoder(ticker)}/stats", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def borrower_lookup( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Cross-BDC borrower lookup - find a company across all BDC portfolios. + + Returns every BDC that holds this borrower's debt, with position sizes, spreads, and valuations. Reveals syndication patterns and allows cross-lender credit deterioration monitoring. + + Parameters + ---------- + name : str + Borrower/company name + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Borrower positions across BDCs + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/borrowers/{jsonable_encoder(name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def market_overview( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Comprehensive private credit market snapshot from free public sources. + + Returns: + - **Lending standards** (SLOOS): Net % of banks tightening C&I loan standards + - **Credit spreads**: ICE BofA High Yield, BBB, BB, CCC spreads + - **Bank lending**: Total C&I loans outstanding + - **Interest rates**: 10Y Treasury, SOFR + - **Financial conditions**: St. Louis Financial Stress Index, Chicago NFCI + + Data sourced from Federal Reserve (FRED API), updated daily/weekly/quarterly. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Market data snapshot + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/market/overview", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def market_spreads( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Current and historical credit spread data from ICE BofA indices. Returns High Yield, BBB, BB, and CCC spreads with historical trend. + + Parameters + ---------- + history : typing.Optional[int] + Number of historical data points + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Credit spread data + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/market/spreads", + method="GET", + params={ + "history": history, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def lending_standards( + self, *, history: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Federal Reserve Senior Loan Officer Opinion Survey (SLOOS) data. Shows net % of banks tightening or easing C&I loan standards. Leading indicator for private credit market conditions. + + Parameters + ---------- + history : typing.Optional[int] + Number of quarterly data points + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + SLOOS lending standards data + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/market/lending-standards", + method="GET", + params={ + "history": history, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def funds_search( + self, + *, + q: str, + strategy: typing.Optional[CreditAnalysisFundsSearchRequestStrategy] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Search private credit funds via SEC Form D filings. Form D is filed when private funds raise capital under Regulation D. Covers fund formations, managers, and capital raised. + + Parameters + ---------- + q : str + Fund name, manager name, or keyword + + strategy : typing.Optional[CreditAnalysisFundsSearchRequestStrategy] + Filter by strategy + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Matching funds + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/funds/search", + method="GET", + params={ + "q": q, + "strategy": strategy, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def fund_formations( + self, + *, + days_back: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Recent private credit fund formations from SEC Form D filings. Shows new funds launching in the private credit space. + + Parameters + ---------- + days_back : typing.Optional[int] + Look back period in days + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Recent fund formations + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/funds/formations", + method="GET", + params={ + "days_back": days_back, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_managers( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + List known private credit fund managers with their strategies and SEC CIK numbers. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + List of credit managers + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/funds/managers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def manager_detail( + self, name: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Detailed information about a private credit fund manager. Combines SEC filing data with known manager intelligence. Returns filing history, fund count, strategy, and recent Form D filings. + + Parameters + ---------- + name : str + Manager name (e.g., 'Ares Management') + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Manager details + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/funds/managers/{jsonable_encoder(name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def sba_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + state: typing.Optional[str] = None, + naics: typing.Optional[str] = None, + status: typing.Optional[CreditAnalysisSbaSearchRequestStatus] = None, + min_amount: typing.Optional[float] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Search SBA 7(a) loan data. Contains loan-level data with borrower, lender, terms, and performance for 100,000+ loans (FY2020-present). + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower name (partial match) + + lender : typing.Optional[str] + Bank/lender name (partial match) + + state : typing.Optional[str] + State code (e.g., CA, NY, TX) + + naics : typing.Optional[str] + NAICS code prefix (e.g., 5112 for software) + + status : typing.Optional[CreditAnalysisSbaSearchRequestStatus] + Loan status: CHGOFF (charged off), PIF (paid in full) + + min_amount : typing.Optional[float] + Minimum gross approval amount ($) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Matching SBA loans + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/sba/search", + method="GET", + params={ + "borrower": borrower, + "lender": lender, + "state": state, + "naics": naics, + "status": status, + "min_amount": min_amount, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def sba_stats( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Aggregate SBA 7(a) loan statistics. Returns default rates, average loan size, top states, and top industries from 100,000+ indexed loans. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + SBA aggregate statistics + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/sba/stats", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def agreements_search( + self, + *, + borrower: typing.Optional[str] = None, + lender: typing.Optional[str] = None, + date_from: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Search SEC 8-K filings for credit agreements. When public companies enter material credit facilities, they file the agreement as an exhibit to an 8-K. + + Parameters + ---------- + borrower : typing.Optional[str] + Borrower/company name + + lender : typing.Optional[str] + Lender/agent name + + date_from : typing.Optional[str] + Start date (YYYY-MM-DD) + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Matching credit agreement filings + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/agreements/search", + method="GET", + params={ + "borrower": borrower, + "lender": lender, + "date_from": date_from, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def agreement_detail( + self, company: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Get the most recent credit agreement filing for a company. Locates the 8-K filing containing the credit agreement. + + Parameters + ---------- + company : str + Company name + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Credit agreement detail + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/agreements/{jsonable_encoder(company)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def nport_funds( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + List tracked private credit interval funds that file N-PORT. These filings contain loan-by-loan holdings. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + List of tracked funds + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/nport/funds", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def nport_search( + self, *, q: str, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Search N-PORT filings for private credit holdings matching a borrower name or keyword. + + Parameters + ---------- + q : str + Borrower name or keyword + + limit : typing.Optional[int] + Maximum results + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Matching N-PORT holdings + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/nport/search", + method="GET", + params={ + "q": q, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def nport_fund_filings( + self, ticker: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Get N-PORT filing list for a specific private credit interval fund. + + Parameters + ---------- + ticker : str + Fund ticker (e.g., CCLFX, AFT) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Fund N-PORT filings + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/datasets/odyssey/credit-analysis/nport/{jsonable_encoder(ticker)}/filings", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def relationships_search( + self, + *, + lender: typing.Optional[str] = None, + borrower: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Search lender-borrower relationships across BDC portfolios. Provide either `lender` (to find their borrowers) or `borrower` (to find their lenders). + + Parameters + ---------- + lender : typing.Optional[str] + Lender name - find their borrowers + + borrower : typing.Optional[str] + Borrower name - find their lenders + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Matching relationships + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/relationships/search", + method="GET", + params={ + "lender": lender, + "borrower": borrower, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def ucc_portals( + self, *, state: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Get UCC filing search portal information for free state-level databases. Returns URLs for CA, NY, TX, FL, IL portals and a list of known major private credit lenders. + + Parameters + ---------- + state : typing.Optional[str] + State code (e.g., CA, NY). Omit for all states. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + UCC portal information + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/odyssey/credit-analysis/relationships/portals", + method="GET", + params={ + "state": state, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_=typing.Dict[str, typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/credit_analysis/types/__init__.py b/src/runcaptain/credit_analysis/types/__init__.py new file mode 100644 index 0000000..1d3b367 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/__init__.py @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .credit_analysis_bdc_search_request_seniority import CreditAnalysisBdcSearchRequestSeniority + from .credit_analysis_funds_search_request_strategy import CreditAnalysisFundsSearchRequestStrategy + from .credit_analysis_news_bulk_response import CreditAnalysisNewsBulkResponse + from .credit_analysis_news_bulk_response_results_item import CreditAnalysisNewsBulkResponseResultsItem + from .credit_analysis_news_detail_response import CreditAnalysisNewsDetailResponse + from .credit_analysis_news_recent_response import CreditAnalysisNewsRecentResponse + from .credit_analysis_news_recent_response_results_item import CreditAnalysisNewsRecentResponseResultsItem + from .credit_analysis_news_search_response import CreditAnalysisNewsSearchResponse + from .credit_analysis_news_search_response_results_item import CreditAnalysisNewsSearchResponseResultsItem + from .credit_analysis_sba_search_request_status import CreditAnalysisSbaSearchRequestStatus +_dynamic_imports: typing.Dict[str, str] = { + "CreditAnalysisBdcSearchRequestSeniority": ".credit_analysis_bdc_search_request_seniority", + "CreditAnalysisFundsSearchRequestStrategy": ".credit_analysis_funds_search_request_strategy", + "CreditAnalysisNewsBulkResponse": ".credit_analysis_news_bulk_response", + "CreditAnalysisNewsBulkResponseResultsItem": ".credit_analysis_news_bulk_response_results_item", + "CreditAnalysisNewsDetailResponse": ".credit_analysis_news_detail_response", + "CreditAnalysisNewsRecentResponse": ".credit_analysis_news_recent_response", + "CreditAnalysisNewsRecentResponseResultsItem": ".credit_analysis_news_recent_response_results_item", + "CreditAnalysisNewsSearchResponse": ".credit_analysis_news_search_response", + "CreditAnalysisNewsSearchResponseResultsItem": ".credit_analysis_news_search_response_results_item", + "CreditAnalysisSbaSearchRequestStatus": ".credit_analysis_sba_search_request_status", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreditAnalysisBdcSearchRequestSeniority", + "CreditAnalysisFundsSearchRequestStrategy", + "CreditAnalysisNewsBulkResponse", + "CreditAnalysisNewsBulkResponseResultsItem", + "CreditAnalysisNewsDetailResponse", + "CreditAnalysisNewsRecentResponse", + "CreditAnalysisNewsRecentResponseResultsItem", + "CreditAnalysisNewsSearchResponse", + "CreditAnalysisNewsSearchResponseResultsItem", + "CreditAnalysisSbaSearchRequestStatus", +] diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_bdc_search_request_seniority.py b/src/runcaptain/credit_analysis/types/credit_analysis_bdc_search_request_seniority.py new file mode 100644 index 0000000..afa819b --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_bdc_search_request_seniority.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreditAnalysisBdcSearchRequestSeniority = typing.Union[ + typing.Literal["first_lien", "second_lien", "unitranche", "mezzanine", "equity"], typing.Any +] diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_funds_search_request_strategy.py b/src/runcaptain/credit_analysis/types/credit_analysis_funds_search_request_strategy.py new file mode 100644 index 0000000..b40f725 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_funds_search_request_strategy.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreditAnalysisFundsSearchRequestStrategy = typing.Union[ + typing.Literal["direct_lending", "mezzanine", "distressed", "venture_lending", "clo"], typing.Any +] diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response.py new file mode 100644 index 0000000..4bfa8c1 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .credit_analysis_news_bulk_response_results_item import CreditAnalysisNewsBulkResponseResultsItem + + +class CreditAnalysisNewsBulkResponse(UniversalBaseModel): + results: typing.Optional[typing.List[CreditAnalysisNewsBulkResponseResultsItem]] = None + total_queries: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of queries processed + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response_results_item.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response_results_item.py new file mode 100644 index 0000000..83a3644 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_bulk_response_results_item.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreditAnalysisNewsBulkResponseResultsItem(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + Original query + """ + + matches: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = pydantic.Field(default=None) + """ + Matching news articles + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_detail_response.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_detail_response.py new file mode 100644 index 0000000..2dbf813 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_detail_response.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreditAnalysisNewsDetailResponse(UniversalBaseModel): + news_id: typing.Optional[str] = pydantic.Field(default=None) + """ + News article ID + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Article title + """ + + content: typing.Optional[str] = pydantic.Field(default=None) + """ + Full article content + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + News source + """ + + published_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Publication date + """ + + category: typing.Optional[str] = pydantic.Field(default=None) + """ + News category + """ + + related_entities: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Related entity names + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response.py new file mode 100644 index 0000000..6139bc5 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .credit_analysis_news_recent_response_results_item import CreditAnalysisNewsRecentResponseResultsItem + + +class CreditAnalysisNewsRecentResponse(UniversalBaseModel): + results: typing.Optional[typing.List[CreditAnalysisNewsRecentResponseResultsItem]] = None + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Results limit + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response_results_item.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response_results_item.py new file mode 100644 index 0000000..3d88d8e --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_recent_response_results_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreditAnalysisNewsRecentResponseResultsItem(UniversalBaseModel): + news_id: typing.Optional[str] = pydantic.Field(default=None) + """ + News article ID + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Article title + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + News source + """ + + published_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Publication date + """ + + category: typing.Optional[str] = pydantic.Field(default=None) + """ + News category + """ + + snippet: typing.Optional[str] = pydantic.Field(default=None) + """ + Article excerpt + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_search_response.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_search_response.py new file mode 100644 index 0000000..555af19 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_search_response.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .credit_analysis_news_search_response_results_item import CreditAnalysisNewsSearchResponseResultsItem + + +class CreditAnalysisNewsSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + Search query + """ + + results: typing.Optional[typing.List[CreditAnalysisNewsSearchResponseResultsItem]] = None + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Results limit + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_news_search_response_results_item.py b/src/runcaptain/credit_analysis/types/credit_analysis_news_search_response_results_item.py new file mode 100644 index 0000000..e1eacec --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_news_search_response_results_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreditAnalysisNewsSearchResponseResultsItem(UniversalBaseModel): + news_id: typing.Optional[str] = pydantic.Field(default=None) + """ + News article ID + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Article title + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + News source + """ + + published_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Publication date + """ + + category: typing.Optional[str] = pydantic.Field(default=None) + """ + News category + """ + + snippet: typing.Optional[str] = pydantic.Field(default=None) + """ + Article excerpt + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/credit_analysis/types/credit_analysis_sba_search_request_status.py b/src/runcaptain/credit_analysis/types/credit_analysis_sba_search_request_status.py new file mode 100644 index 0000000..1d96844 --- /dev/null +++ b/src/runcaptain/credit_analysis/types/credit_analysis_sba_search_request_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreditAnalysisSbaSearchRequestStatus = typing.Union[typing.Literal["CHGOFF", "PIF"], typing.Any] diff --git a/src/runcaptain/datasets/client.py b/src/runcaptain/datasets/client.py index ab9c729..5ddc618 100644 --- a/src/runcaptain/datasets/client.py +++ b/src/runcaptain/datasets/client.py @@ -6,6 +6,7 @@ from ..core.request_options import RequestOptions from ..types.dataset_article_response import DatasetArticleResponse from ..types.dataset_search_response import DatasetSearchResponse +from ..types.scientific_ask_response import ScientificAskResponse from .raw_client import AsyncRawDatasetsClient, RawDatasetsClient from .types.batch_search_datasets_response import BatchSearchDatasetsResponse @@ -34,6 +35,7 @@ def search_dataset( *, q: str, limit: typing.Optional[int] = None, + author: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> DatasetSearchResponse: """ @@ -55,6 +57,9 @@ def search_dataset( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` — returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -72,11 +77,14 @@ def search_dataset( key="YOUR_KEY", ) client.datasets.search_dataset( - dataset="dataset_name", - q="climate change policy", + dataset="nytimes", + q="latest technology trends", + limit=10, ) """ - _response = self._raw_client.search_dataset(dataset, q=q, limit=limit, request_options=request_options) + _response = self._raw_client.search_dataset( + dataset, q=q, limit=limit, author=author, request_options=request_options + ) return _response.data def batch_search_datasets( @@ -85,6 +93,7 @@ def batch_search_datasets( q: str, datasets: typing.Optional[typing.Sequence[str]] = OMIT, limit: typing.Optional[int] = OMIT, + author: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> BatchSearchDatasetsResponse: """ @@ -92,19 +101,10 @@ def batch_search_datasets( Searches the same query across all specified datasets simultaneously. If no datasets are specified, searches all available datasets. - ## Supported Datasets - - `nytimes` - New York Times - - `washpost` - Washington Post - - `sfstandard` - SF Standard - - `sacbee` - Sacramento Bee - - `sfchronicle` - San Francisco Chronicle - - `newyorker` - The New Yorker - - `theatlantic` - The Atlantic - - `sjmercury` - San Jose Mercury News - - `latimes` - Los Angeles Times + Contact your Account Executive for available datasets. ## Response - Returns results grouped by dataset source, with title, URL, snippet, and date for each article. + Returns results grouped by dataset source, with title, URL, snippet, author, and date for each article. Parameters ---------- @@ -117,6 +117,9 @@ def batch_search_datasets( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` - returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -135,12 +138,12 @@ def batch_search_datasets( ) client.datasets.batch_search_datasets( q="artificial intelligence regulation", - datasets=["nytimes", "washpost", "theatlantic"], limit=10, + datasets=["nytimes", "washpost", "theatlantic"], ) """ _response = self._raw_client.batch_search_datasets( - q=q, datasets=datasets, limit=limit, request_options=request_options + q=q, datasets=datasets, limit=limit, author=author, request_options=request_options ) return _response.data @@ -189,6 +192,74 @@ def get_dataset_article( _response = self._raw_client.get_dataset_article(dataset, url, request_options=request_options) return _response.data + def search_medical_papers( + self, + *, + question: str, + max_sources: typing.Optional[int] = OMIT, + include_trials: typing.Optional[bool] = OMIT, + recency_years: typing.Optional[int] = OMIT, + stream: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScientificAskResponse: + """ + Search medical and biomedical papers with a natural-language question. + Federates PubMed, PMC full-text, ClinicalTrials.gov, and Semantic Scholar, + then synthesizes a cited answer. + + `stream=true` returns text/event-stream with `tool_use`, `tool_result_summary`, + `text_delta`, and `done` event types. + + Parameters + ---------- + question : str + Natural-language question. + + max_sources : typing.Optional[int] + Target number of cited sources in the final answer. + + include_trials : typing.Optional[bool] + Whether the agent may call ClinicalTrials.gov. + + recency_years : typing.Optional[int] + Prefer evidence within the last N years where the question allows. + + stream : typing.Optional[bool] + If true, response is text/event-stream; otherwise JSON. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScientificAskResponse + Synthesized answer with cited sources. Returns JSON by default; when `stream=true` the response is `text/event-stream` with `tool_use`, `tool_result_summary`, `text_delta`, and `done` events. + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.datasets.search_medical_papers( + question="What is the evidence that BRCA1-mutated breast cancer patients benefit from PARP inhibitors?", + max_sources=10, + include_trials=True, + recency_years=10, + ) + """ + _response = self._raw_client.search_medical_papers( + question=question, + max_sources=max_sources, + include_trials=include_trials, + recency_years=recency_years, + stream=stream, + request_options=request_options, + ) + return _response.data + class AsyncDatasetsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -211,6 +282,7 @@ async def search_dataset( *, q: str, limit: typing.Optional[int] = None, + author: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> DatasetSearchResponse: """ @@ -232,6 +304,9 @@ async def search_dataset( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` — returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -254,14 +329,17 @@ async def search_dataset( async def main() -> None: await client.datasets.search_dataset( - dataset="dataset_name", - q="climate change policy", + dataset="nytimes", + q="latest technology trends", + limit=10, ) asyncio.run(main()) """ - _response = await self._raw_client.search_dataset(dataset, q=q, limit=limit, request_options=request_options) + _response = await self._raw_client.search_dataset( + dataset, q=q, limit=limit, author=author, request_options=request_options + ) return _response.data async def batch_search_datasets( @@ -270,6 +348,7 @@ async def batch_search_datasets( q: str, datasets: typing.Optional[typing.Sequence[str]] = OMIT, limit: typing.Optional[int] = OMIT, + author: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> BatchSearchDatasetsResponse: """ @@ -277,19 +356,10 @@ async def batch_search_datasets( Searches the same query across all specified datasets simultaneously. If no datasets are specified, searches all available datasets. - ## Supported Datasets - - `nytimes` - New York Times - - `washpost` - Washington Post - - `sfstandard` - SF Standard - - `sacbee` - Sacramento Bee - - `sfchronicle` - San Francisco Chronicle - - `newyorker` - The New Yorker - - `theatlantic` - The Atlantic - - `sjmercury` - San Jose Mercury News - - `latimes` - Los Angeles Times + Contact your Account Executive for available datasets. ## Response - Returns results grouped by dataset source, with title, URL, snippet, and date for each article. + Returns results grouped by dataset source, with title, URL, snippet, author, and date for each article. Parameters ---------- @@ -302,6 +372,9 @@ async def batch_search_datasets( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` - returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -325,15 +398,15 @@ async def batch_search_datasets( async def main() -> None: await client.datasets.batch_search_datasets( q="artificial intelligence regulation", - datasets=["nytimes", "washpost", "theatlantic"], limit=10, + datasets=["nytimes", "washpost", "theatlantic"], ) asyncio.run(main()) """ _response = await self._raw_client.batch_search_datasets( - q=q, datasets=datasets, limit=limit, request_options=request_options + q=q, datasets=datasets, limit=limit, author=author, request_options=request_options ) return _response.data @@ -389,3 +462,79 @@ async def main() -> None: """ _response = await self._raw_client.get_dataset_article(dataset, url, request_options=request_options) return _response.data + + async def search_medical_papers( + self, + *, + question: str, + max_sources: typing.Optional[int] = OMIT, + include_trials: typing.Optional[bool] = OMIT, + recency_years: typing.Optional[int] = OMIT, + stream: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScientificAskResponse: + """ + Search medical and biomedical papers with a natural-language question. + Federates PubMed, PMC full-text, ClinicalTrials.gov, and Semantic Scholar, + then synthesizes a cited answer. + + `stream=true` returns text/event-stream with `tool_use`, `tool_result_summary`, + `text_delta`, and `done` event types. + + Parameters + ---------- + question : str + Natural-language question. + + max_sources : typing.Optional[int] + Target number of cited sources in the final answer. + + include_trials : typing.Optional[bool] + Whether the agent may call ClinicalTrials.gov. + + recency_years : typing.Optional[int] + Prefer evidence within the last N years where the question allows. + + stream : typing.Optional[bool] + If true, response is text/event-stream; otherwise JSON. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScientificAskResponse + Synthesized answer with cited sources. Returns JSON by default; when `stream=true` the response is `text/event-stream` with `tool_use`, `tool_result_summary`, `text_delta`, and `done` events. + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.datasets.search_medical_papers( + question="What is the evidence that BRCA1-mutated breast cancer patients benefit from PARP inhibitors?", + max_sources=10, + include_trials=True, + recency_years=10, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.search_medical_papers( + question=question, + max_sources=max_sources, + include_trials=include_trials, + recency_years=recency_years, + stream=stream, + request_options=request_options, + ) + return _response.data diff --git a/src/runcaptain/datasets/raw_client.py b/src/runcaptain/datasets/raw_client.py index ea402cd..6d8e7aa 100644 --- a/src/runcaptain/datasets/raw_client.py +++ b/src/runcaptain/datasets/raw_client.py @@ -7,15 +7,20 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.bad_request_error import BadRequestError from ..errors.forbidden_error import ForbiddenError from ..errors.service_unavailable_error import ServiceUnavailableError from ..errors.unauthorized_error import UnauthorizedError +from ..errors.unprocessable_entity_error import UnprocessableEntityError from ..types.dataset_article_response import DatasetArticleResponse from ..types.dataset_search_response import DatasetSearchResponse +from ..types.http_validation_error import HttpValidationError +from ..types.scientific_ask_response import ScientificAskResponse from .types.batch_search_datasets_response import BatchSearchDatasetsResponse +from pydantic import ValidationError # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -31,6 +36,7 @@ def search_dataset( *, q: str, limit: typing.Optional[int] = None, + author: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[DatasetSearchResponse]: """ @@ -52,6 +58,9 @@ def search_dataset( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` — returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -66,6 +75,7 @@ def search_dataset( params={ "q": q, "limit": limit, + "author": author, }, request_options=request_options, ) @@ -126,6 +136,10 @@ def search_dataset( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def batch_search_datasets( @@ -134,6 +148,7 @@ def batch_search_datasets( q: str, datasets: typing.Optional[typing.Sequence[str]] = OMIT, limit: typing.Optional[int] = OMIT, + author: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[BatchSearchDatasetsResponse]: """ @@ -141,19 +156,10 @@ def batch_search_datasets( Searches the same query across all specified datasets simultaneously. If no datasets are specified, searches all available datasets. - ## Supported Datasets - - `nytimes` - New York Times - - `washpost` - Washington Post - - `sfstandard` - SF Standard - - `sacbee` - Sacramento Bee - - `sfchronicle` - San Francisco Chronicle - - `newyorker` - The New Yorker - - `theatlantic` - The Atlantic - - `sjmercury` - San Jose Mercury News - - `latimes` - Los Angeles Times + Contact your Account Executive for available datasets. ## Response - Returns results grouped by dataset source, with title, URL, snippet, and date for each article. + Returns results grouped by dataset source, with title, URL, snippet, author, and date for each article. Parameters ---------- @@ -166,6 +172,9 @@ def batch_search_datasets( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` - returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -181,6 +190,7 @@ def batch_search_datasets( "q": q, "datasets": datasets, "limit": limit, + "author": author, }, headers={ "content-type": "application/json", @@ -234,6 +244,10 @@ def batch_search_datasets( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def get_dataset_article( @@ -327,6 +341,132 @@ def get_dataset_article( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def search_medical_papers( + self, + *, + question: str, + max_sources: typing.Optional[int] = OMIT, + include_trials: typing.Optional[bool] = OMIT, + recency_years: typing.Optional[int] = OMIT, + stream: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ScientificAskResponse]: + """ + Search medical and biomedical papers with a natural-language question. + Federates PubMed, PMC full-text, ClinicalTrials.gov, and Semantic Scholar, + then synthesizes a cited answer. + + `stream=true` returns text/event-stream with `tool_use`, `tool_result_summary`, + `text_delta`, and `done` event types. + + Parameters + ---------- + question : str + Natural-language question. + + max_sources : typing.Optional[int] + Target number of cited sources in the final answer. + + include_trials : typing.Optional[bool] + Whether the agent may call ClinicalTrials.gov. + + recency_years : typing.Optional[int] + Prefer evidence within the last N years where the question allows. + + stream : typing.Optional[bool] + If true, response is text/event-stream; otherwise JSON. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScientificAskResponse] + Synthesized answer with cited sources. Returns JSON by default; when `stream=true` the response is `text/event-stream` with `tool_use`, `tool_result_summary`, `text_delta`, and `done` events. + """ + _response = self._client_wrapper.httpx_client.request( + "v2/datasets/scientific/medical/ask", + method="POST", + json={ + "question": question, + "max_sources": max_sources, + "include_trials": include_trials, + "recency_years": recency_years, + "stream": stream, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScientificAskResponse, + parse_obj_as( + type_=ScientificAskResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -340,6 +480,7 @@ async def search_dataset( *, q: str, limit: typing.Optional[int] = None, + author: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[DatasetSearchResponse]: """ @@ -361,6 +502,9 @@ async def search_dataset( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` — returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -375,6 +519,7 @@ async def search_dataset( params={ "q": q, "limit": limit, + "author": author, }, request_options=request_options, ) @@ -435,6 +580,10 @@ async def search_dataset( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def batch_search_datasets( @@ -443,6 +592,7 @@ async def batch_search_datasets( q: str, datasets: typing.Optional[typing.Sequence[str]] = OMIT, limit: typing.Optional[int] = OMIT, + author: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[BatchSearchDatasetsResponse]: """ @@ -450,19 +600,10 @@ async def batch_search_datasets( Searches the same query across all specified datasets simultaneously. If no datasets are specified, searches all available datasets. - ## Supported Datasets - - `nytimes` - New York Times - - `washpost` - Washington Post - - `sfstandard` - SF Standard - - `sacbee` - Sacramento Bee - - `sfchronicle` - San Francisco Chronicle - - `newyorker` - The New Yorker - - `theatlantic` - The Atlantic - - `sjmercury` - San Jose Mercury News - - `latimes` - Los Angeles Times + Contact your Account Executive for available datasets. ## Response - Returns results grouped by dataset source, with title, URL, snippet, and date for each article. + Returns results grouped by dataset source, with title, URL, snippet, author, and date for each article. Parameters ---------- @@ -475,6 +616,9 @@ async def batch_search_datasets( limit : typing.Optional[int] Maximum number of results to return (default: 10, max: 100) + author : typing.Optional[str] + Filter results by author/byline name. Used as an AND condition with `q` - returns only articles matching BOTH the query topic AND the specified author. For all articles by an author regardless of topic, use a broad query like `q=*` with `author`. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -490,6 +634,7 @@ async def batch_search_datasets( "q": q, "datasets": datasets, "limit": limit, + "author": author, }, headers={ "content-type": "application/json", @@ -543,6 +688,10 @@ async def batch_search_datasets( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def get_dataset_article( @@ -636,4 +785,130 @@ async def get_dataset_article( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def search_medical_papers( + self, + *, + question: str, + max_sources: typing.Optional[int] = OMIT, + include_trials: typing.Optional[bool] = OMIT, + recency_years: typing.Optional[int] = OMIT, + stream: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ScientificAskResponse]: + """ + Search medical and biomedical papers with a natural-language question. + Federates PubMed, PMC full-text, ClinicalTrials.gov, and Semantic Scholar, + then synthesizes a cited answer. + + `stream=true` returns text/event-stream with `tool_use`, `tool_result_summary`, + `text_delta`, and `done` event types. + + Parameters + ---------- + question : str + Natural-language question. + + max_sources : typing.Optional[int] + Target number of cited sources in the final answer. + + include_trials : typing.Optional[bool] + Whether the agent may call ClinicalTrials.gov. + + recency_years : typing.Optional[int] + Prefer evidence within the last N years where the question allows. + + stream : typing.Optional[bool] + If true, response is text/event-stream; otherwise JSON. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScientificAskResponse] + Synthesized answer with cited sources. Returns JSON by default; when `stream=true` the response is `text/event-stream` with `tool_use`, `tool_result_summary`, `text_delta`, and `done` events. + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/datasets/scientific/medical/ask", + method="POST", + json={ + "question": question, + "max_sources": max_sources, + "include_trials": include_trials, + "recency_years": recency_years, + "stream": stream, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScientificAskResponse, + parse_obj_as( + type_=ScientificAskResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/deals/__init__.py b/src/runcaptain/deals/__init__.py index 5cde020..2b2da49 100644 --- a/src/runcaptain/deals/__init__.py +++ b/src/runcaptain/deals/__init__.py @@ -2,3 +2,72 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + DealsBioResponse, + DealsDebtLendersResponse, + DealsDebtLendersResponseLendersItem, + DealsInvestorsResponse, + DealsInvestorsResponseParticipantsItem, + DealsSearchResponse, + DealsSearchResponseResultsItem, + DealsServiceProvidersResponse, + DealsServiceProvidersResponseServiceProvidersItem, + DealsStockInfoResponse, + DealsValuationResponse, + DealsValuationResponseTerms, + ) +_dynamic_imports: typing.Dict[str, str] = { + "DealsBioResponse": ".types", + "DealsDebtLendersResponse": ".types", + "DealsDebtLendersResponseLendersItem": ".types", + "DealsInvestorsResponse": ".types", + "DealsInvestorsResponseParticipantsItem": ".types", + "DealsSearchResponse": ".types", + "DealsSearchResponseResultsItem": ".types", + "DealsServiceProvidersResponse": ".types", + "DealsServiceProvidersResponseServiceProvidersItem": ".types", + "DealsStockInfoResponse": ".types", + "DealsValuationResponse": ".types", + "DealsValuationResponseTerms": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "DealsBioResponse", + "DealsDebtLendersResponse", + "DealsDebtLendersResponseLendersItem", + "DealsInvestorsResponse", + "DealsInvestorsResponseParticipantsItem", + "DealsSearchResponse", + "DealsSearchResponseResultsItem", + "DealsServiceProvidersResponse", + "DealsServiceProvidersResponseServiceProvidersItem", + "DealsStockInfoResponse", + "DealsValuationResponse", + "DealsValuationResponseTerms", +] diff --git a/src/runcaptain/deals/client.py b/src/runcaptain/deals/client.py index 5dbfae2..b1a4e1b 100644 --- a/src/runcaptain/deals/client.py +++ b/src/runcaptain/deals/client.py @@ -5,6 +5,13 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawDealsClient, RawDealsClient +from .types.deals_bio_response import DealsBioResponse +from .types.deals_debt_lenders_response import DealsDebtLendersResponse +from .types.deals_investors_response import DealsInvestorsResponse +from .types.deals_search_response import DealsSearchResponse +from .types.deals_service_providers_response import DealsServiceProvidersResponse +from .types.deals_stock_info_response import DealsStockInfoResponse +from .types.deals_valuation_response import DealsValuationResponse class DealsClient: @@ -25,6 +32,8 @@ def with_raw_response(self) -> RawDealsClient: def search( self, *, + q: typing.Optional[str] = None, + company: typing.Optional[str] = None, deal_type: typing.Optional[str] = None, min_amount: typing.Optional[float] = None, max_amount: typing.Optional[float] = None, @@ -33,14 +42,20 @@ def search( page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> DealsSearchResponse: """ Search for funding rounds and deals by company, deal type, amount range, or date. Returns matching transactions with deal type, amount, date, and participants. Use this to find deal entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Company name or deal keyword (e.g., 'OpenAI Series B') + + company : typing.Optional[str] + Filter by company name or domain (e.g., 'OpenAI') + deal_type : typing.Optional[str] - Filter by deal type + Filter by deal type (e.g., 'series_a', 'series_b', 'seed', 'ipo', 'acquisition', 'debt') min_amount : typing.Optional[float] Minimum deal amount @@ -65,7 +80,7 @@ def search( Returns ------- - typing.Dict[str, typing.Any] + DealsSearchResponse Successful response Examples @@ -79,6 +94,8 @@ def search( client.deals.search() """ _response = self._raw_client.search( + q=q, + company=company, deal_type=deal_type, min_amount=min_amount, max_amount=max_amount, @@ -90,20 +107,21 @@ def search( ) return _response.data - def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DealsBioResponse: """ Get comprehensive deal information including amount, type, date, company, and investor participants. This is the primary endpoint for deal overview data. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsBioResponse Successful response Examples @@ -121,55 +139,21 @@ def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = Non _response = self._raw_client.bio(id, request_options=request_options) return _response.data - def detailed( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get comprehensive deal data combining bio, investors, valuation, and terms in a single response. Use this for complete deal intelligence without multiple API calls. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.deals.detailed( - id="deal_openai_0", - ) - """ - _response = self._raw_client.detailed(id, request_options=request_options) - return _response.data - - def investors( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def investors(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DealsInvestorsResponse: """ Get all investors participating in the deal including lead and follow-on investors. Returns investor names, roles, and investment amounts. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsInvestorsResponse Successful response Examples @@ -189,20 +173,21 @@ def investors( def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsServiceProvidersResponse: """ Get service providers involved in the deal including legal counsel, investment bankers, and financial advisors. Returns firm names and service types. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsServiceProvidersResponse Successful response Examples @@ -220,22 +205,21 @@ def service_providers( _response = self._raw_client.service_providers(id, request_options=request_options) return _response.data - def valuation( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def valuation(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DealsValuationResponse: """ Get valuation information including pre-money and post-money valuations, equity percentage, and pricing details. Useful for understanding deal economics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsValuationResponse Successful response Examples @@ -253,22 +237,21 @@ def valuation( _response = self._raw_client.valuation(id, request_options=request_options) return _response.data - def stock_info( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def stock_info(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DealsStockInfoResponse: """ Get current stock price and market data for the company involved in this deal. Only applicable for public companies. Returns real-time stock quotes and market metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsStockInfoResponse Successful response Examples @@ -286,23 +269,21 @@ def stock_info( _response = self._raw_client.stock_info(id, request_options=request_options) return _response.data - def cap_table( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def cap_table(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get capitalization table showing ownership breakdown after the deal. Will return equity ownership percentages, share counts, and investor stakes when implemented. + **Coming Soon** - Get capitalization table showing ownership breakdown after the deal. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -319,23 +300,21 @@ def cap_table( _response = self._raw_client.cap_table(id, request_options=request_options) return _response.data - def tranche( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def tranche(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get information about deal tranches and payment schedules for structured financing. Will return tranche amounts, dates, and conditions when implemented. + **Coming Soon** - Get information about deal tranches and payment schedules for structured financing. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -354,20 +333,21 @@ def tranche( def debt_lenders( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsDebtLendersResponse: """ Get lenders participating in debt financing deals. Returns lender names, amounts, and terms for venture debt and credit facilities. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsDebtLendersResponse Successful response Examples @@ -385,23 +365,21 @@ def debt_lenders( _response = self._raw_client.debt_lenders(id, request_options=request_options) return _response.data - def multiples( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def multiples(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Will return detailed valuation analysis when implemented. + **Coming Soon** - Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -418,39 +396,6 @@ def multiples( _response = self._raw_client.multiples(id, request_options=request_options) return _response.data - def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to deal information. Returns history of changes to deal data with timestamps and modified fields. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.deals.updates( - id="deal_openai_0", - ) - """ - _response = self._raw_client.updates(id, request_options=request_options) - return _response.data - class AsyncDealsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -470,6 +415,8 @@ def with_raw_response(self) -> AsyncRawDealsClient: async def search( self, *, + q: typing.Optional[str] = None, + company: typing.Optional[str] = None, deal_type: typing.Optional[str] = None, min_amount: typing.Optional[float] = None, max_amount: typing.Optional[float] = None, @@ -478,14 +425,20 @@ async def search( page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> DealsSearchResponse: """ Search for funding rounds and deals by company, deal type, amount range, or date. Returns matching transactions with deal type, amount, date, and participants. Use this to find deal entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Company name or deal keyword (e.g., 'OpenAI Series B') + + company : typing.Optional[str] + Filter by company name or domain (e.g., 'OpenAI') + deal_type : typing.Optional[str] - Filter by deal type + Filter by deal type (e.g., 'series_a', 'series_b', 'seed', 'ipo', 'acquisition', 'debt') min_amount : typing.Optional[float] Minimum deal amount @@ -510,7 +463,7 @@ async def search( Returns ------- - typing.Dict[str, typing.Any] + DealsSearchResponse Successful response Examples @@ -532,6 +485,8 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.search( + q=q, + company=company, deal_type=deal_type, min_amount=min_amount, max_amount=max_amount, @@ -543,22 +498,21 @@ async def main() -> None: ) return _response.data - async def bio( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DealsBioResponse: """ Get comprehensive deal information including amount, type, date, company, and investor participants. This is the primary endpoint for deal overview data. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsBioResponse Successful response Examples @@ -584,63 +538,23 @@ async def main() -> None: _response = await self._raw_client.bio(id, request_options=request_options) return _response.data - async def detailed( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get comprehensive deal data combining bio, investors, valuation, and terms in a single response. Use this for complete deal intelligence without multiple API calls. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.deals.detailed( - id="deal_openai_0", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.detailed(id, request_options=request_options) - return _response.data - async def investors( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsInvestorsResponse: """ Get all investors participating in the deal including lead and follow-on investors. Returns investor names, roles, and investment amounts. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsInvestorsResponse Successful response Examples @@ -668,20 +582,21 @@ async def main() -> None: async def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsServiceProvidersResponse: """ Get service providers involved in the deal including legal counsel, investment bankers, and financial advisors. Returns firm names and service types. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsServiceProvidersResponse Successful response Examples @@ -709,20 +624,21 @@ async def main() -> None: async def valuation( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsValuationResponse: """ Get valuation information including pre-money and post-money valuations, equity percentage, and pricing details. Useful for understanding deal economics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsValuationResponse Successful response Examples @@ -750,20 +666,21 @@ async def main() -> None: async def stock_info( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsStockInfoResponse: """ Get current stock price and market data for the company involved in this deal. Only applicable for public companies. Returns real-time stock quotes and market metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsStockInfoResponse Successful response Examples @@ -789,23 +706,21 @@ async def main() -> None: _response = await self._raw_client.stock_info(id, request_options=request_options) return _response.data - async def cap_table( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def cap_table(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get capitalization table showing ownership breakdown after the deal. Will return equity ownership percentages, share counts, and investor stakes when implemented. + **Coming Soon** - Get capitalization table showing ownership breakdown after the deal. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -830,23 +745,21 @@ async def main() -> None: _response = await self._raw_client.cap_table(id, request_options=request_options) return _response.data - async def tranche( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def tranche(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get information about deal tranches and payment schedules for structured financing. Will return tranche amounts, dates, and conditions when implemented. + **Coming Soon** - Get information about deal tranches and payment schedules for structured financing. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -873,20 +786,21 @@ async def main() -> None: async def debt_lenders( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> DealsDebtLendersResponse: """ Get lenders participating in debt financing deals. Returns lender names, amounts, and terms for venture debt and credit facilities. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + DealsDebtLendersResponse Successful response Examples @@ -912,23 +826,21 @@ async def main() -> None: _response = await self._raw_client.debt_lenders(id, request_options=request_options) return _response.data - async def multiples( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def multiples(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Will return detailed valuation analysis when implemented. + **Coming Soon** - Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -952,44 +864,3 @@ async def main() -> None: """ _response = await self._raw_client.multiples(id, request_options=request_options) return _response.data - - async def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to deal information. Returns history of changes to deal data with timestamps and modified fields. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.deals.updates( - id="deal_openai_0", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.updates(id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/deals/raw_client.py b/src/runcaptain/deals/raw_client.py index 5c852af..2f90d60 100644 --- a/src/runcaptain/deals/raw_client.py +++ b/src/runcaptain/deals/raw_client.py @@ -7,11 +7,20 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.deals_bio_response import DealsBioResponse +from .types.deals_debt_lenders_response import DealsDebtLendersResponse +from .types.deals_investors_response import DealsInvestorsResponse +from .types.deals_search_response import DealsSearchResponse +from .types.deals_service_providers_response import DealsServiceProvidersResponse +from .types.deals_stock_info_response import DealsStockInfoResponse +from .types.deals_valuation_response import DealsValuationResponse +from pydantic import ValidationError class RawDealsClient: @@ -21,6 +30,8 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def search( self, *, + q: typing.Optional[str] = None, + company: typing.Optional[str] = None, deal_type: typing.Optional[str] = None, min_amount: typing.Optional[float] = None, max_amount: typing.Optional[float] = None, @@ -29,14 +40,20 @@ def search( page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsSearchResponse]: """ Search for funding rounds and deals by company, deal type, amount range, or date. Returns matching transactions with deal type, amount, date, and participants. Use this to find deal entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Company name or deal keyword (e.g., 'OpenAI Series B') + + company : typing.Optional[str] + Filter by company name or domain (e.g., 'OpenAI') + deal_type : typing.Optional[str] - Filter by deal type + Filter by deal type (e.g., 'series_a', 'series_b', 'seed', 'ipo', 'acquisition', 'debt') min_amount : typing.Optional[float] Minimum deal amount @@ -61,13 +78,15 @@ def search( Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/deals/search", method="GET", params={ + "q": q, + "company": company, "deal_type": deal_type, "min_amount": min_amount, "max_amount": max_amount, @@ -81,9 +100,9 @@ def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -102,24 +121,29 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def bio( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsBioResponse]: """ Get comprehensive deal information including amount, type, date, company, and investor participants. This is the primary endpoint for deal overview data. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsBioResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -130,69 +154,9 @@ def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def detailed( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get comprehensive deal data combining bio, investors, valuation, and terms in a single response. Use this for complete deal intelligence without multiple API calls. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/detailed", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], + DealsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -222,24 +186,29 @@ def detailed( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def investors( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsInvestorsResponse]: """ Get all investors participating in the deal including lead and follow-on investors. Returns investor names, roles, and investment amounts. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsInvestorsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -250,9 +219,9 @@ def investors( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsInvestorsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsInvestorsResponse, # type: ignore object_=_response.json(), ), ) @@ -282,24 +251,29 @@ def investors( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsServiceProvidersResponse]: """ Get service providers involved in the deal including legal counsel, investment bankers, and financial advisors. Returns firm names and service types. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsServiceProvidersResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -310,9 +284,9 @@ def service_providers( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsServiceProvidersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsServiceProvidersResponse, # type: ignore object_=_response.json(), ), ) @@ -353,24 +327,29 @@ def service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def valuation( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsValuationResponse]: """ Get valuation information including pre-money and post-money valuations, equity percentage, and pricing details. Useful for understanding deal economics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsValuationResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -381,9 +360,9 @@ def valuation( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsValuationResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsValuationResponse, # type: ignore object_=_response.json(), ), ) @@ -413,24 +392,29 @@ def valuation( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def stock_info( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsStockInfoResponse]: """ Get current stock price and market data for the company involved in this deal. Only applicable for public companies. Returns real-time stock quotes and market metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsStockInfoResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -441,9 +425,9 @@ def stock_info( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsStockInfoResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsStockInfoResponse, # type: ignore object_=_response.json(), ), ) @@ -484,25 +468,27 @@ def stock_info( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def cap_table( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + def cap_table(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: """ - Coming Soon: Get capitalization table showing ownership breakdown after the deal. Will return equity ownership percentages, share counts, and investor stakes when implemented. + **Coming Soon** - Get capitalization table showing ownership breakdown after the deal. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/cap-table", @@ -511,14 +497,7 @@ def cap_table( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -555,25 +534,27 @@ def cap_table( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def tranche( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + def tranche(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: """ - Coming Soon: Get information about deal tranches and payment schedules for structured financing. Will return tranche amounts, dates, and conditions when implemented. + **Coming Soon** - Get information about deal tranches and payment schedules for structured financing. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/tranche", @@ -582,14 +563,7 @@ def tranche( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -626,24 +600,29 @@ def tranche( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def debt_lenders( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[DealsDebtLendersResponse]: """ Get lenders participating in debt financing deals. Returns lender names, amounts, and terms for venture debt and credit facilities. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[DealsDebtLendersResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -654,9 +633,9 @@ def debt_lenders( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsDebtLendersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsDebtLendersResponse, # type: ignore object_=_response.json(), ), ) @@ -686,25 +665,27 @@ def debt_lenders( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def multiples( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + def multiples(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: """ - Coming Soon: Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Will return detailed valuation analysis when implemented. + **Coming Soon** - Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/multiples", @@ -713,14 +694,7 @@ def multiples( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -757,66 +731,10 @@ def multiples( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to deal information. Returns history of changes to deal data with timestamps and modified fields. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -827,6 +745,8 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def search( self, *, + q: typing.Optional[str] = None, + company: typing.Optional[str] = None, deal_type: typing.Optional[str] = None, min_amount: typing.Optional[float] = None, max_amount: typing.Optional[float] = None, @@ -835,14 +755,20 @@ async def search( page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsSearchResponse]: """ Search for funding rounds and deals by company, deal type, amount range, or date. Returns matching transactions with deal type, amount, date, and participants. Use this to find deal entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Company name or deal keyword (e.g., 'OpenAI Series B') + + company : typing.Optional[str] + Filter by company name or domain (e.g., 'OpenAI') + deal_type : typing.Optional[str] - Filter by deal type + Filter by deal type (e.g., 'series_a', 'series_b', 'seed', 'ipo', 'acquisition', 'debt') min_amount : typing.Optional[float] Minimum deal amount @@ -867,13 +793,15 @@ async def search( Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/deals/search", method="GET", params={ + "q": q, + "company": company, "deal_type": deal_type, "min_amount": min_amount, "max_amount": max_amount, @@ -887,9 +815,9 @@ async def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -908,24 +836,29 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def bio( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsBioResponse]: """ Get comprehensive deal information including amount, type, date, company, and investor participants. This is the primary endpoint for deal overview data. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsBioResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -936,69 +869,9 @@ async def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def detailed( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get comprehensive deal data combining bio, investors, valuation, and terms in a single response. Use this for complete deal intelligence without multiple API calls. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/detailed", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -1028,24 +901,29 @@ async def detailed( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def investors( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsInvestorsResponse]: """ Get all investors participating in the deal including lead and follow-on investors. Returns investor names, roles, and investment amounts. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsInvestorsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1056,9 +934,9 @@ async def investors( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsInvestorsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsInvestorsResponse, # type: ignore object_=_response.json(), ), ) @@ -1088,24 +966,29 @@ async def investors( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsServiceProvidersResponse]: """ Get service providers involved in the deal including legal counsel, investment bankers, and financial advisors. Returns firm names and service types. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsServiceProvidersResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1116,9 +999,9 @@ async def service_providers( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsServiceProvidersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsServiceProvidersResponse, # type: ignore object_=_response.json(), ), ) @@ -1159,24 +1042,29 @@ async def service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def valuation( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsValuationResponse]: """ Get valuation information including pre-money and post-money valuations, equity percentage, and pricing details. Useful for understanding deal economics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsValuationResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1187,9 +1075,9 @@ async def valuation( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsValuationResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsValuationResponse, # type: ignore object_=_response.json(), ), ) @@ -1219,24 +1107,29 @@ async def valuation( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def stock_info( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsStockInfoResponse]: """ Get current stock price and market data for the company involved in this deal. Only applicable for public companies. Returns real-time stock quotes and market metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsStockInfoResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1247,9 +1140,9 @@ async def stock_info( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsStockInfoResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsStockInfoResponse, # type: ignore object_=_response.json(), ), ) @@ -1290,25 +1183,29 @@ async def stock_info( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def cap_table( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get capitalization table showing ownership breakdown after the deal. Will return equity ownership percentages, share counts, and investor stakes when implemented. + **Coming Soon** - Get capitalization table showing ownership breakdown after the deal. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/cap-table", @@ -1317,14 +1214,7 @@ async def cap_table( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1361,25 +1251,29 @@ async def cap_table( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def tranche( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get information about deal tranches and payment schedules for structured financing. Will return tranche amounts, dates, and conditions when implemented. + **Coming Soon** - Get information about deal tranches and payment schedules for structured financing. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/tranche", @@ -1388,14 +1282,7 @@ async def tranche( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1432,24 +1319,29 @@ async def tranche( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def debt_lenders( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[DealsDebtLendersResponse]: """ Get lenders participating in debt financing deals. Returns lender names, amounts, and terms for venture debt and credit facilities. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[DealsDebtLendersResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1460,9 +1352,9 @@ async def debt_lenders( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + DealsDebtLendersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=DealsDebtLendersResponse, # type: ignore object_=_response.json(), ), ) @@ -1492,25 +1384,29 @@ async def debt_lenders( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def multiples( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Will return detailed valuation analysis when implemented. + **Coming Soon** - Get valuation multiples for the deal including revenue multiple, EBITDA multiple, and comparable transaction metrics. Parameters ---------- id : str + Deal entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/multiples", @@ -1519,14 +1415,7 @@ async def multiples( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1563,64 +1452,8 @@ async def multiples( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to deal information. Returns history of changes to deal data with timestamps and modified fields. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/deals/{jsonable_encoder(id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/deals/types/__init__.py b/src/runcaptain/deals/types/__init__.py new file mode 100644 index 0000000..6b5cb55 --- /dev/null +++ b/src/runcaptain/deals/types/__init__.py @@ -0,0 +1,73 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .deals_bio_response import DealsBioResponse + from .deals_debt_lenders_response import DealsDebtLendersResponse + from .deals_debt_lenders_response_lenders_item import DealsDebtLendersResponseLendersItem + from .deals_investors_response import DealsInvestorsResponse + from .deals_investors_response_participants_item import DealsInvestorsResponseParticipantsItem + from .deals_search_response import DealsSearchResponse + from .deals_search_response_results_item import DealsSearchResponseResultsItem + from .deals_service_providers_response import DealsServiceProvidersResponse + from .deals_service_providers_response_service_providers_item import ( + DealsServiceProvidersResponseServiceProvidersItem, + ) + from .deals_stock_info_response import DealsStockInfoResponse + from .deals_valuation_response import DealsValuationResponse + from .deals_valuation_response_terms import DealsValuationResponseTerms +_dynamic_imports: typing.Dict[str, str] = { + "DealsBioResponse": ".deals_bio_response", + "DealsDebtLendersResponse": ".deals_debt_lenders_response", + "DealsDebtLendersResponseLendersItem": ".deals_debt_lenders_response_lenders_item", + "DealsInvestorsResponse": ".deals_investors_response", + "DealsInvestorsResponseParticipantsItem": ".deals_investors_response_participants_item", + "DealsSearchResponse": ".deals_search_response", + "DealsSearchResponseResultsItem": ".deals_search_response_results_item", + "DealsServiceProvidersResponse": ".deals_service_providers_response", + "DealsServiceProvidersResponseServiceProvidersItem": ".deals_service_providers_response_service_providers_item", + "DealsStockInfoResponse": ".deals_stock_info_response", + "DealsValuationResponse": ".deals_valuation_response", + "DealsValuationResponseTerms": ".deals_valuation_response_terms", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "DealsBioResponse", + "DealsDebtLendersResponse", + "DealsDebtLendersResponseLendersItem", + "DealsInvestorsResponse", + "DealsInvestorsResponseParticipantsItem", + "DealsSearchResponse", + "DealsSearchResponseResultsItem", + "DealsServiceProvidersResponse", + "DealsServiceProvidersResponseServiceProvidersItem", + "DealsStockInfoResponse", + "DealsValuationResponse", + "DealsValuationResponseTerms", +] diff --git a/src/runcaptain/deals/types/deals_bio_response.py b/src/runcaptain/deals/types/deals_bio_response.py new file mode 100644 index 0000000..f59476a --- /dev/null +++ b/src/runcaptain/deals/types/deals_bio_response.py @@ -0,0 +1,83 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsBioResponse(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique deal identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Always 'deal' + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Target company name + """ + + company_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + deal_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal date (YYYY-MM-DD) + """ + + deal_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal type + """ + + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Deal amount in USD + """ + + currency: typing.Optional[str] = pydantic.Field(default=None) + """ + Currency code + """ + + num_investors: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of investors + """ + + lead_investors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Lead investor names + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal description + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal status + """ + + data_sources: typing.Optional[typing.List[str]] = None + cached_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Cache timestamp (ISO 8601) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_debt_lenders_response.py b/src/runcaptain/deals/types/deals_debt_lenders_response.py new file mode 100644 index 0000000..9c7f921 --- /dev/null +++ b/src/runcaptain/deals/types/deals_debt_lenders_response.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .deals_debt_lenders_response_lenders_item import DealsDebtLendersResponseLendersItem + + +class DealsDebtLendersResponse(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal identifier + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + service_providers: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + total: typing.Optional[int] = None + data_sources: typing.Optional[typing.List[str]] = None + is_debt_round: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether this deal includes debt financing + """ + + lenders: typing.Optional[typing.List[DealsDebtLendersResponseLendersItem]] = pydantic.Field(default=None) + """ + Debt lenders in the deal + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_debt_lenders_response_lenders_item.py b/src/runcaptain/deals/types/deals_debt_lenders_response_lenders_item.py new file mode 100644 index 0000000..cce7240 --- /dev/null +++ b/src/runcaptain/deals/types/deals_debt_lenders_response_lenders_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsDebtLendersResponseLendersItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Lender name + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Lender role (lead, participant) + """ + + type: typing.Optional[str] = pydantic.Field(default=None) + """ + Lender type + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_investors_response.py b/src/runcaptain/deals/types/deals_investors_response.py new file mode 100644 index 0000000..73265de --- /dev/null +++ b/src/runcaptain/deals/types/deals_investors_response.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .deals_investors_response_participants_item import DealsInvestorsResponseParticipantsItem + + +class DealsInvestorsResponse(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal identifier + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Target company name + """ + + participants: typing.Optional[typing.List[DealsInvestorsResponseParticipantsItem]] = None + total_participants: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of participants + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_investors_response_participants_item.py b/src/runcaptain/deals/types/deals_investors_response_participants_item.py new file mode 100644 index 0000000..96d3caa --- /dev/null +++ b/src/runcaptain/deals/types/deals_investors_response_participants_item.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsInvestorsResponseParticipantsItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Role in deal (lead, participant) + """ + + amount_invested: typing.Optional[float] = pydantic.Field(default=None) + """ + Amount invested in USD + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_search_response.py b/src/runcaptain/deals/types/deals_search_response.py new file mode 100644 index 0000000..77b54db --- /dev/null +++ b/src/runcaptain/deals/types/deals_search_response.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .deals_search_response_results_item import DealsSearchResponseResultsItem + + +class DealsSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[DealsSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + page: typing.Optional[int] = pydantic.Field(default=None) + """ + Current page number + """ + + page_size: typing.Optional[int] = pydantic.Field(default=None) + """ + Results per page + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_search_response_results_item.py b/src/runcaptain/deals/types/deals_search_response_results_item.py new file mode 100644 index 0000000..3e31396 --- /dev/null +++ b/src/runcaptain/deals/types/deals_search_response_results_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsSearchResponseResultsItem(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique deal identifier + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Target company name + """ + + deal_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal date (YYYY-MM-DD) + """ + + deal_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal type (e.g., Series A, acquisition) + """ + + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Deal amount in USD + """ + + currency: typing.Optional[str] = pydantic.Field(default=None) + """ + Currency code + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_service_providers_response.py b/src/runcaptain/deals/types/deals_service_providers_response.py new file mode 100644 index 0000000..1451414 --- /dev/null +++ b/src/runcaptain/deals/types/deals_service_providers_response.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .deals_service_providers_response_service_providers_item import DealsServiceProvidersResponseServiceProvidersItem + + +class DealsServiceProvidersResponse(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal identifier + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Target company name + """ + + service_providers: typing.Optional[typing.List[DealsServiceProvidersResponseServiceProvidersItem]] = None + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of service providers + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_service_providers_response_service_providers_item.py b/src/runcaptain/deals/types/deals_service_providers_response_service_providers_item.py new file mode 100644 index 0000000..3f45892 --- /dev/null +++ b/src/runcaptain/deals/types/deals_service_providers_response_service_providers_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsServiceProvidersResponseServiceProvidersItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider name + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service type + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Role in deal + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_stock_info_response.py b/src/runcaptain/deals/types/deals_stock_info_response.py new file mode 100644 index 0000000..9adbfbd --- /dev/null +++ b/src/runcaptain/deals/types/deals_stock_info_response.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsStockInfoResponse(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal identifier + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + service_providers: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + total: typing.Optional[int] = None + data_sources: typing.Optional[typing.List[str]] = None + search_results: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = pydantic.Field(default=None) + """ + Stock/equity related search results + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_valuation_response.py b/src/runcaptain/deals/types/deals_valuation_response.py new file mode 100644 index 0000000..272bde8 --- /dev/null +++ b/src/runcaptain/deals/types/deals_valuation_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .deals_valuation_response_terms import DealsValuationResponseTerms + + +class DealsValuationResponse(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal identifier + """ + + terms: typing.Optional[DealsValuationResponseTerms] = None + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/deals/types/deals_valuation_response_terms.py b/src/runcaptain/deals/types/deals_valuation_response_terms.py new file mode 100644 index 0000000..0710900 --- /dev/null +++ b/src/runcaptain/deals/types/deals_valuation_response_terms.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DealsValuationResponseTerms(UniversalBaseModel): + deal_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal type + """ + + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Deal amount in USD + """ + + valuation_pre_money: typing.Optional[float] = pydantic.Field(default=None) + """ + Pre-money valuation in USD + """ + + valuation_post_money: typing.Optional[float] = pydantic.Field(default=None) + """ + Post-money valuation in USD + """ + + currency: typing.Optional[str] = pydantic.Field(default=None) + """ + Currency code + """ + + equity_percentage: typing.Optional[float] = pydantic.Field(default=None) + """ + Equity percentage + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/errors/__init__.py b/src/runcaptain/errors/__init__.py index 948df85..f9a986e 100644 --- a/src/runcaptain/errors/__init__.py +++ b/src/runcaptain/errors/__init__.py @@ -13,6 +13,7 @@ from .not_implemented_error import NotImplementedError from .service_unavailable_error import ServiceUnavailableError from .unauthorized_error import UnauthorizedError + from .unprocessable_entity_error import UnprocessableEntityError _dynamic_imports: typing.Dict[str, str] = { "BadRequestError": ".bad_request_error", "ConflictError": ".conflict_error", @@ -21,6 +22,7 @@ "NotImplementedError": ".not_implemented_error", "ServiceUnavailableError": ".service_unavailable_error", "UnauthorizedError": ".unauthorized_error", + "UnprocessableEntityError": ".unprocessable_entity_error", } @@ -53,4 +55,5 @@ def __dir__(): "NotImplementedError", "ServiceUnavailableError", "UnauthorizedError", + "UnprocessableEntityError", ] diff --git a/src/runcaptain/errors/unprocessable_entity_error.py b/src/runcaptain/errors/unprocessable_entity_error.py new file mode 100644 index 0000000..d3f9c5d --- /dev/null +++ b/src/runcaptain/errors/unprocessable_entity_error.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError +from ..types.http_validation_error import HttpValidationError + + +class UnprocessableEntityError(ApiError): + def __init__(self, body: HttpValidationError, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=422, headers=headers, body=body) diff --git a/src/runcaptain/fundamentals/__init__.py b/src/runcaptain/fundamentals/__init__.py deleted file mode 100644 index 5cde020..0000000 --- a/src/runcaptain/fundamentals/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - diff --git a/src/runcaptain/funds/__init__.py b/src/runcaptain/funds/__init__.py index 5cde020..a0e1616 100644 --- a/src/runcaptain/funds/__init__.py +++ b/src/runcaptain/funds/__init__.py @@ -2,3 +2,57 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + FundsActiveInvestmentsResponse, + FundsActiveInvestmentsResponseInvestmentsItem, + FundsBioResponse, + FundsBioResponseData, + FundsSearchResponse, + FundsSearchResponseResultsItem, + FundsSearchResponseResultsItemLocation, + ) +_dynamic_imports: typing.Dict[str, str] = { + "FundsActiveInvestmentsResponse": ".types", + "FundsActiveInvestmentsResponseInvestmentsItem": ".types", + "FundsBioResponse": ".types", + "FundsBioResponseData": ".types", + "FundsSearchResponse": ".types", + "FundsSearchResponseResultsItem": ".types", + "FundsSearchResponseResultsItemLocation": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "FundsActiveInvestmentsResponse", + "FundsActiveInvestmentsResponseInvestmentsItem", + "FundsBioResponse", + "FundsBioResponseData", + "FundsSearchResponse", + "FundsSearchResponseResultsItem", + "FundsSearchResponseResultsItemLocation", +] diff --git a/src/runcaptain/funds/client.py b/src/runcaptain/funds/client.py index d6c51ff..7cfd562 100644 --- a/src/runcaptain/funds/client.py +++ b/src/runcaptain/funds/client.py @@ -5,6 +5,9 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawFundsClient, RawFundsClient +from .types.funds_active_investments_response import FundsActiveInvestmentsResponse +from .types.funds_bio_response import FundsBioResponse +from .types.funds_search_response import FundsSearchResponse class FundsClient: @@ -25,27 +28,35 @@ def with_raw_response(self) -> RawFundsClient: def search( self, *, + q: str, + fund_type: typing.Optional[str] = None, vintage_year: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> FundsSearchResponse: """ Search for venture capital and private equity funds by name. Returns matching fund profiles with size, vintage year, and investment focus. Use this to find fund entity IDs for detailed lookups. Parameters ---------- + q : str + Fund name or keyword (e.g., 'Sequoia Capital Fund XIV') + + fund_type : typing.Optional[str] + Filter by fund type (e.g., 'venture', 'buyout', 'growth_equity', 'real_estate', 'debt') + vintage_year : typing.Optional[int] - Filter by vintage year + Filter by fund vintage year (e.g., 2023) limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundsSearchResponse Successful response Examples @@ -56,27 +67,31 @@ def search( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.funds.search() + client.funds.search( + q="Sequoia Capital Fund", + limit=10, + ) """ - _response = self._raw_client.search(vintage_year=vintage_year, limit=limit, request_options=request_options) + _response = self._raw_client.search( + q=q, fund_type=fund_type, vintage_year=vintage_year, limit=limit, request_options=request_options + ) return _response.data - def bio( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def bio(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> FundsBioResponse: """ Get comprehensive fund profile including fund size, vintage year, investment strategy, stage focus, and sector focus. This is the primary endpoint for fund overview data. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundsBioResponse Successful response Examples @@ -94,23 +109,21 @@ def bio( _response = self._raw_client.bio(fund_id, request_options=request_options) return _response.data - def performance( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def performance(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Will return LP-level performance data when implemented with proprietary fund reporting. + **Coming Soon** - Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -128,54 +141,35 @@ def performance( return _response.data def active_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + fund_id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> FundsActiveInvestmentsResponse: """ Get current portfolio companies invested by this specific fund. Returns company names, investment amounts, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- fund_id : str + Fund entity ID - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.funds.active_investments( - fund_id="sequoia", - ) - """ - _response = self._raw_client.active_investments(fund_id, request_options=request_options) - return _response.data - - def all_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete investment history for this specific fund including current and exited positions. Returns all companies backed by the fund with outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - fund_id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundsActiveInvestmentsResponse Successful response Examples @@ -186,30 +180,30 @@ def all_investments( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.funds.all_investments( + client.funds.active_investments( fund_id="sequoia", ) """ - _response = self._raw_client.all_investments(fund_id, request_options=request_options) + _response = self._raw_client.active_investments( + fund_id, page=page, page_size=page_size, request_options=request_options + ) return _response.data - def commitments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def commitments(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. + **Coming Soon** - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -226,23 +220,21 @@ def commitments( _response = self._raw_client.commitments(fund_id, request_options=request_options) return _response.data - def cash_flows( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def cash_flows(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get fund cash flow history including capital calls and distributions. Will return quarterly cash flow statements when implemented with LP reporting data. + **Coming Soon** - Get fund cash flow history including capital calls and distributions. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -259,23 +251,21 @@ def cash_flows( _response = self._raw_client.cash_flows(fund_id, request_options=request_options) return _response.data - def benchmark( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def benchmark(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get fund performance compared to industry benchmarks and peer funds. Will return quartile rankings and comparative metrics when implemented. + **Coming Soon** - Get fund performance compared to industry benchmarks and peer funds. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -292,56 +282,21 @@ def benchmark( _response = self._raw_client.benchmark(fund_id, request_options=request_options) return _response.data - def people( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get investment team members for the fund including partners, principals, and associates. Returns names, titles, and LinkedIn profiles. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.funds.people( - fund_id="sequoia", - ) - """ - _response = self._raw_client.people(fund_id, request_options=request_options) - return _response.data - - def preferences( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def preferences(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. + **Coming Soon** - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -358,39 +313,6 @@ def preferences( _response = self._raw_client.preferences(fund_id, request_options=request_options) return _response.data - def updates( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to fund profile data. Returns history of changes including new investments, exits, and team changes with timestamps. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.funds.updates( - fund_id="sequoia", - ) - """ - _response = self._raw_client.updates(fund_id, request_options=request_options) - return _response.data - class AsyncFundsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -410,27 +332,35 @@ def with_raw_response(self) -> AsyncRawFundsClient: async def search( self, *, + q: str, + fund_type: typing.Optional[str] = None, vintage_year: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> FundsSearchResponse: """ Search for venture capital and private equity funds by name. Returns matching fund profiles with size, vintage year, and investment focus. Use this to find fund entity IDs for detailed lookups. Parameters ---------- + q : str + Fund name or keyword (e.g., 'Sequoia Capital Fund XIV') + + fund_type : typing.Optional[str] + Filter by fund type (e.g., 'venture', 'buyout', 'growth_equity', 'real_estate', 'debt') + vintage_year : typing.Optional[int] - Filter by vintage year + Filter by fund vintage year (e.g., 2023) limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundsSearchResponse Successful response Examples @@ -446,32 +376,34 @@ async def search( async def main() -> None: - await client.funds.search() + await client.funds.search( + q="Sequoia Capital Fund", + limit=10, + ) asyncio.run(main()) """ _response = await self._raw_client.search( - vintage_year=vintage_year, limit=limit, request_options=request_options + q=q, fund_type=fund_type, vintage_year=vintage_year, limit=limit, request_options=request_options ) return _response.data - async def bio( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def bio(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> FundsBioResponse: """ Get comprehensive fund profile including fund size, vintage year, investment strategy, stage focus, and sector focus. This is the primary endpoint for fund overview data. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundsBioResponse Successful response Examples @@ -497,23 +429,21 @@ async def main() -> None: _response = await self._raw_client.bio(fund_id, request_options=request_options) return _response.data - async def performance( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def performance(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Will return LP-level performance data when implemented with proprietary fund reporting. + **Coming Soon** - Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -539,62 +469,35 @@ async def main() -> None: return _response.data async def active_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + fund_id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> FundsActiveInvestmentsResponse: """ Get current portfolio companies invested by this specific fund. Returns company names, investment amounts, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- fund_id : str + Fund entity ID - request_options : typing.Optional[RequestOptions] - Request-specific configuration. + page : typing.Optional[int] + Page number for pagination (default: 1) - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.funds.active_investments( - fund_id="sequoia", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.active_investments(fund_id, request_options=request_options) - return _response.data - - async def all_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete investment history for this specific fund including current and exited positions. Returns all companies backed by the fund with outcomes. - - Parameters - ---------- - fund_id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundsActiveInvestmentsResponse Successful response Examples @@ -610,33 +513,33 @@ async def all_investments( async def main() -> None: - await client.funds.all_investments( + await client.funds.active_investments( fund_id="sequoia", ) asyncio.run(main()) """ - _response = await self._raw_client.all_investments(fund_id, request_options=request_options) + _response = await self._raw_client.active_investments( + fund_id, page=page, page_size=page_size, request_options=request_options + ) return _response.data - async def commitments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def commitments(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. + **Coming Soon** - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -661,23 +564,21 @@ async def main() -> None: _response = await self._raw_client.commitments(fund_id, request_options=request_options) return _response.data - async def cash_flows( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def cash_flows(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get fund cash flow history including capital calls and distributions. Will return quarterly cash flow statements when implemented with LP reporting data. + **Coming Soon** - Get fund cash flow history including capital calls and distributions. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -702,23 +603,21 @@ async def main() -> None: _response = await self._raw_client.cash_flows(fund_id, request_options=request_options) return _response.data - async def benchmark( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def benchmark(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Coming Soon: Get fund performance compared to industry benchmarks and peer funds. Will return quartile rankings and comparative metrics when implemented. + **Coming Soon** - Get fund performance compared to industry benchmarks and peer funds. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -743,64 +642,21 @@ async def main() -> None: _response = await self._raw_client.benchmark(fund_id, request_options=request_options) return _response.data - async def people( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def preferences(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get investment team members for the fund including partners, principals, and associates. Returns names, titles, and LinkedIn profiles. + **Coming Soon** - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.funds.people( - fund_id="sequoia", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.people(fund_id, request_options=request_options) - return _response.data - - async def preferences( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -824,44 +680,3 @@ async def main() -> None: """ _response = await self._raw_client.preferences(fund_id, request_options=request_options) return _response.data - - async def updates( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to fund profile data. Returns history of changes including new investments, exits, and team changes with timestamps. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.funds.updates( - fund_id="sequoia", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.updates(fund_id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/funds/raw_client.py b/src/runcaptain/funds/raw_client.py index fe6981f..ac242d7 100644 --- a/src/runcaptain/funds/raw_client.py +++ b/src/runcaptain/funds/raw_client.py @@ -7,11 +7,16 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.funds_active_investments_response import FundsActiveInvestmentsResponse +from .types.funds_bio_response import FundsBioResponse +from .types.funds_search_response import FundsSearchResponse +from pydantic import ValidationError class RawFundsClient: @@ -21,33 +26,43 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def search( self, *, + q: str, + fund_type: typing.Optional[str] = None, vintage_year: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[FundsSearchResponse]: """ Search for venture capital and private equity funds by name. Returns matching fund profiles with size, vintage year, and investment focus. Use this to find fund entity IDs for detailed lookups. Parameters ---------- + q : str + Fund name or keyword (e.g., 'Sequoia Capital Fund XIV') + + fund_type : typing.Optional[str] + Filter by fund type (e.g., 'venture', 'buyout', 'growth_equity', 'real_estate', 'debt') + vintage_year : typing.Optional[int] - Filter by vintage year + Filter by fund vintage year (e.g., 2023) limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[FundsSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/funds/search", method="GET", params={ + "q": q, + "fund_type": fund_type, "vintage_year": vintage_year, "limit": limit, }, @@ -56,9 +71,9 @@ def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -77,24 +92,29 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def bio( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[FundsBioResponse]: """ Get comprehensive fund profile including fund size, vintage year, investment strategy, stage focus, and sector focus. This is the primary endpoint for fund overview data. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[FundsBioResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -105,9 +125,9 @@ def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -137,25 +157,29 @@ def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def performance( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Coming Soon: Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Will return LP-level performance data when implemented with proprietary fund reporting. + **Coming Soon** - Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/performance", @@ -164,14 +188,7 @@ def performance( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -208,97 +225,59 @@ def performance( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def active_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + fund_id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[FundsActiveInvestmentsResponse]: """ Get current portfolio companies invested by this specific fund. Returns company names, investment amounts, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- fund_id : str + Fund entity ID - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/active-investments", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def all_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete investment history for this specific fund including current and exited positions. Returns all companies backed by the fund with outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - fund_id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[FundsActiveInvestmentsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/all-investments", + f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/active-investments", method="GET", + params={ + "page": page, + "page_size": page_size, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundsActiveInvestmentsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundsActiveInvestmentsResponse, # type: ignore object_=_response.json(), ), ) @@ -328,25 +307,29 @@ def all_investments( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def commitments( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. + **Coming Soon** - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/commitments", @@ -355,14 +338,7 @@ def commitments( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -399,25 +375,29 @@ def commitments( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def cash_flows( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Coming Soon: Get fund cash flow history including capital calls and distributions. Will return quarterly cash flow statements when implemented with LP reporting data. + **Coming Soon** - Get fund cash flow history including capital calls and distributions. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/cash-flows", @@ -426,14 +406,7 @@ def cash_flows( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -470,25 +443,27 @@ def cash_flows( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def benchmark( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + def benchmark(self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: """ - Coming Soon: Get fund performance compared to industry benchmarks and peer funds. Will return quartile rankings and comparative metrics when implemented. + **Coming Soon** - Get fund performance compared to industry benchmarks and peer funds. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/benchmark", @@ -497,14 +472,7 @@ def benchmark( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -541,85 +509,29 @@ def benchmark( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def people( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get investment team members for the fund including partners, principals, and associates. Returns names, titles, and LinkedIn profiles. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/people", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def preferences( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. + **Coming Soon** - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/preferences", @@ -628,14 +540,7 @@ def preferences( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -672,66 +577,10 @@ def preferences( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def updates( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to fund profile data. Returns history of changes including new investments, exits, and team changes with timestamps. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -742,33 +591,43 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def search( self, *, + q: str, + fund_type: typing.Optional[str] = None, vintage_year: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[FundsSearchResponse]: """ Search for venture capital and private equity funds by name. Returns matching fund profiles with size, vintage year, and investment focus. Use this to find fund entity IDs for detailed lookups. Parameters ---------- + q : str + Fund name or keyword (e.g., 'Sequoia Capital Fund XIV') + + fund_type : typing.Optional[str] + Filter by fund type (e.g., 'venture', 'buyout', 'growth_equity', 'real_estate', 'debt') + vintage_year : typing.Optional[int] - Filter by vintage year + Filter by fund vintage year (e.g., 2023) limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[FundsSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/funds/search", method="GET", params={ + "q": q, + "fund_type": fund_type, "vintage_year": vintage_year, "limit": limit, }, @@ -777,9 +636,9 @@ async def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -798,24 +657,29 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def bio( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[FundsBioResponse]: """ Get comprehensive fund profile including fund size, vintage year, investment strategy, stage focus, and sector focus. This is the primary endpoint for fund overview data. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[FundsBioResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -826,9 +690,9 @@ async def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -858,25 +722,29 @@ async def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def performance( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Will return LP-level performance data when implemented with proprietary fund reporting. + **Coming Soon** - Get fund performance metrics including IRR, TVPI, DPI, and RVPI. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/performance", @@ -885,14 +753,7 @@ async def performance( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -929,97 +790,59 @@ async def performance( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def active_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + fund_id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[FundsActiveInvestmentsResponse]: """ Get current portfolio companies invested by this specific fund. Returns company names, investment amounts, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- fund_id : str + Fund entity ID - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/active-investments", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def all_investments( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete investment history for this specific fund including current and exited positions. Returns all companies backed by the fund with outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - fund_id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[FundsActiveInvestmentsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/all-investments", + f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/active-investments", method="GET", + params={ + "page": page, + "page_size": page_size, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundsActiveInvestmentsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundsActiveInvestmentsResponse, # type: ignore object_=_response.json(), ), ) @@ -1049,25 +872,29 @@ async def all_investments( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def commitments( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. + **Coming Soon** - Get limited partner commitments to the fund including LP names and commitment amounts. Returns investor base and capital structure. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/commitments", @@ -1076,14 +903,7 @@ async def commitments( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1120,25 +940,29 @@ async def commitments( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def cash_flows( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get fund cash flow history including capital calls and distributions. Will return quarterly cash flow statements when implemented with LP reporting data. + **Coming Soon** - Get fund cash flow history including capital calls and distributions. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/cash-flows", @@ -1147,14 +971,7 @@ async def cash_flows( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1191,25 +1008,29 @@ async def cash_flows( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def benchmark( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Coming Soon: Get fund performance compared to industry benchmarks and peer funds. Will return quartile rankings and comparative metrics when implemented. + **Coming Soon** - Get fund performance compared to industry benchmarks and peer funds. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/benchmark", @@ -1218,14 +1039,7 @@ async def benchmark( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1262,85 +1076,29 @@ async def benchmark( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def people( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get investment team members for the fund including partners, principals, and associates. Returns names, titles, and LinkedIn profiles. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/people", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def preferences( self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. + **Coming Soon** - Get investment criteria and preferences for the fund including stage focus, sector preferences, geography, and check size ranges. Parameters ---------- fund_id : str + Fund entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/preferences", @@ -1349,14 +1107,7 @@ async def preferences( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1393,64 +1144,8 @@ async def preferences( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def updates( - self, fund_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to fund profile data. Returns history of changes including new investments, exits, and team changes with timestamps. - - Parameters - ---------- - fund_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/funds/{jsonable_encoder(fund_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/funds/types/__init__.py b/src/runcaptain/funds/types/__init__.py new file mode 100644 index 0000000..8a6ad02 --- /dev/null +++ b/src/runcaptain/funds/types/__init__.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .funds_active_investments_response import FundsActiveInvestmentsResponse + from .funds_active_investments_response_investments_item import FundsActiveInvestmentsResponseInvestmentsItem + from .funds_bio_response import FundsBioResponse + from .funds_bio_response_data import FundsBioResponseData + from .funds_search_response import FundsSearchResponse + from .funds_search_response_results_item import FundsSearchResponseResultsItem + from .funds_search_response_results_item_location import FundsSearchResponseResultsItemLocation +_dynamic_imports: typing.Dict[str, str] = { + "FundsActiveInvestmentsResponse": ".funds_active_investments_response", + "FundsActiveInvestmentsResponseInvestmentsItem": ".funds_active_investments_response_investments_item", + "FundsBioResponse": ".funds_bio_response", + "FundsBioResponseData": ".funds_bio_response_data", + "FundsSearchResponse": ".funds_search_response", + "FundsSearchResponseResultsItem": ".funds_search_response_results_item", + "FundsSearchResponseResultsItemLocation": ".funds_search_response_results_item_location", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "FundsActiveInvestmentsResponse", + "FundsActiveInvestmentsResponseInvestmentsItem", + "FundsBioResponse", + "FundsBioResponseData", + "FundsSearchResponse", + "FundsSearchResponseResultsItem", + "FundsSearchResponseResultsItemLocation", +] diff --git a/src/runcaptain/funds/types/funds_active_investments_response.py b/src/runcaptain/funds/types/funds_active_investments_response.py new file mode 100644 index 0000000..d0bc60a --- /dev/null +++ b/src/runcaptain/funds/types/funds_active_investments_response.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .funds_active_investments_response_investments_item import FundsActiveInvestmentsResponseInvestmentsItem + + +class FundsActiveInvestmentsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund entity ID + """ + + investments: typing.Optional[typing.List[FundsActiveInvestmentsResponseInvestmentsItem]] = None + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total investments + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Results limit + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/funds/types/funds_active_investments_response_investments_item.py b/src/runcaptain/funds/types/funds_active_investments_response_investments_item.py new file mode 100644 index 0000000..ca2fbd4 --- /dev/null +++ b/src/runcaptain/funds/types/funds_active_investments_response_investments_item.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundsActiveInvestmentsResponseInvestmentsItem(UniversalBaseModel): + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + company_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + investment_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Investment date + """ + + funding_round: typing.Optional[str] = pydantic.Field(default=None) + """ + Funding round + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Investment status + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/funds/types/funds_bio_response.py b/src/runcaptain/funds/types/funds_bio_response.py new file mode 100644 index 0000000..474ce6b --- /dev/null +++ b/src/runcaptain/funds/types/funds_bio_response.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .funds_bio_response_data import FundsBioResponseData + + +class FundsBioResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund entity ID + """ + + identifier: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Always 'fund' + """ + + data: typing.Optional[FundsBioResponseData] = None + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Data source + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund industry classification + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund description/summary + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/funds/types/funds_bio_response_data.py b/src/runcaptain/funds/types/funds_bio_response_data.py new file mode 100644 index 0000000..47a02b1 --- /dev/null +++ b/src/runcaptain/funds/types/funds_bio_response_data.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundsBioResponseData(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + founded: typing.Optional[int] = pydantic.Field(default=None) + """ + Year founded + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund industry classification + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund description/summary + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/funds/types/funds_search_response.py b/src/runcaptain/funds/types/funds_search_response.py new file mode 100644 index 0000000..a705777 --- /dev/null +++ b/src/runcaptain/funds/types/funds_search_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .funds_search_response_results_item import FundsSearchResponseResultsItem + + +class FundsSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[FundsSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum results requested + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/funds/types/funds_search_response_results_item.py b/src/runcaptain/funds/types/funds_search_response_results_item.py new file mode 100644 index 0000000..5bdcb73 --- /dev/null +++ b/src/runcaptain/funds/types/funds_search_response_results_item.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .funds_search_response_results_item_location import FundsSearchResponseResultsItemLocation + + +class FundsSearchResponseResultsItem(UniversalBaseModel): + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Industry classification + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + location: typing.Optional[FundsSearchResponseResultsItemLocation] = None + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Data source + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/funds/types/funds_search_response_results_item_location.py b/src/runcaptain/funds/types/funds_search_response_results_item_location.py new file mode 100644 index 0000000..12f295b --- /dev/null +++ b/src/runcaptain/funds/types/funds_search_response_results_item_location.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundsSearchResponseResultsItemLocation(UniversalBaseModel): + locality: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/__init__.py b/src/runcaptain/general/__init__.py index 5cde020..1fbe89a 100644 --- a/src/runcaptain/general/__init__.py +++ b/src/runcaptain/general/__init__.py @@ -2,3 +2,75 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + GeneralEntityAffiliatesResponse, + GeneralEntityAffiliatesResponseAffiliatesItem, + GeneralEntityLocationsResponse, + GeneralEntityLocationsResponseLocationsItem, + GeneralEntityNewsResponse, + GeneralEntityNewsResponseNewsItem, + GeneralEntityPeopleResponse, + GeneralEntityPeopleResponsePeopleItem, + GeneralSearchRequestEntityType, + GeneralSearchResponse, + GeneralSearchResponseResultsItem, + GeneralSearchSharedResponse, + GeneralSearchSharedResponseResultsItem, + ) +_dynamic_imports: typing.Dict[str, str] = { + "GeneralEntityAffiliatesResponse": ".types", + "GeneralEntityAffiliatesResponseAffiliatesItem": ".types", + "GeneralEntityLocationsResponse": ".types", + "GeneralEntityLocationsResponseLocationsItem": ".types", + "GeneralEntityNewsResponse": ".types", + "GeneralEntityNewsResponseNewsItem": ".types", + "GeneralEntityPeopleResponse": ".types", + "GeneralEntityPeopleResponsePeopleItem": ".types", + "GeneralSearchRequestEntityType": ".types", + "GeneralSearchResponse": ".types", + "GeneralSearchResponseResultsItem": ".types", + "GeneralSearchSharedResponse": ".types", + "GeneralSearchSharedResponseResultsItem": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GeneralEntityAffiliatesResponse", + "GeneralEntityAffiliatesResponseAffiliatesItem", + "GeneralEntityLocationsResponse", + "GeneralEntityLocationsResponseLocationsItem", + "GeneralEntityNewsResponse", + "GeneralEntityNewsResponseNewsItem", + "GeneralEntityPeopleResponse", + "GeneralEntityPeopleResponsePeopleItem", + "GeneralSearchRequestEntityType", + "GeneralSearchResponse", + "GeneralSearchResponseResultsItem", + "GeneralSearchSharedResponse", + "GeneralSearchSharedResponseResultsItem", +] diff --git a/src/runcaptain/general/client.py b/src/runcaptain/general/client.py index 97f03b0..a7f6e44 100644 --- a/src/runcaptain/general/client.py +++ b/src/runcaptain/general/client.py @@ -5,6 +5,13 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawGeneralClient, RawGeneralClient +from .types.general_entity_affiliates_response import GeneralEntityAffiliatesResponse +from .types.general_entity_locations_response import GeneralEntityLocationsResponse +from .types.general_entity_news_response import GeneralEntityNewsResponse +from .types.general_entity_people_response import GeneralEntityPeopleResponse +from .types.general_search_request_entity_type import GeneralSearchRequestEntityType +from .types.general_search_response import GeneralSearchResponse +from .types.general_search_shared_response import GeneralSearchSharedResponse class GeneralClient: @@ -23,22 +30,37 @@ def with_raw_response(self) -> RawGeneralClient: return self._raw_client def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: str, + entity_type: typing.Optional[GeneralSearchRequestEntityType] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralSearchResponse: """ - Search across all entity types (companies, people, investors, funds, deals, etc.) in a unified query. Returns matching entities with their type and basic information. Use this for broad discovery before drilling into specific entity types. + Cross-entity search across companies and people. + + Returns the company match first, then executives (C-suite) at that company, then other employees. + + Supports `limit` parameter to control results count (default: 25, max: 100). Parameters ---------- + q : str + Search query across all entity types (companies, people, investors) + + entity_type : typing.Optional[GeneralSearchRequestEntityType] + Filter by entity type + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralSearchResponse Successful response Examples @@ -49,23 +71,40 @@ def search( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.general.search() + client.general.search( + q="AI safety researchers at OpenAI", + limit=10, + ) """ - _response = self._raw_client.search(limit=limit, request_options=request_options) + _response = self._raw_client.search(q=q, entity_type=entity_type, limit=limit, request_options=request_options) return _response.data - def search_shared(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def search_shared( + self, + *, + q: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralSearchSharedResponse: """ - Search for entities shared across your organization. Returns entities that multiple team members have accessed or tagged. Useful for discovering commonly referenced companies, investors, or people within your team. + Shared/saved search across entities. + + Same as cross-entity search with shared filter support. Returns companies and people matching the query. Parameters ---------- + q : typing.Optional[str] + Search query (optional) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralSearchSharedResponse Successful response Examples @@ -78,25 +117,37 @@ def search_shared(self, *, request_options: typing.Optional[RequestOptions] = No ) client.general.search_shared() """ - _response = self._raw_client.search_shared(request_options=request_options) + _response = self._raw_client.search_shared(q=q, limit=limit, request_options=request_options) return _response.data def entity_people( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralEntityPeopleResponse: """ - Get people associated with any entity (company employees, fund team members, investor partners, etc.). Returns names, titles, and LinkedIn profiles for key people. + Get people associated with an entity (company employees/leadership). + + Returns executives first (C-suite, VP, Director), then other employees. Results are deduplicated. + + Supports `limit` parameter (default: 25, max: 100). Parameters ---------- entity_id : str + Entity ID (any type) + + limit : typing.Optional[int] + Maximum results to return (default: 25, max: 100) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityPeopleResponse Successful response Examples @@ -111,25 +162,26 @@ def entity_people( entity_id="openai", ) """ - _response = self._raw_client.entity_people(entity_id, request_options=request_options) + _response = self._raw_client.entity_people(entity_id, limit=limit, request_options=request_options) return _response.data def entity_locations( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> GeneralEntityLocationsResponse: """ Get office locations for any entity. Returns headquarters and branch office addresses with city, state, country, and full address details. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityLocationsResponse Successful response Examples @@ -149,20 +201,21 @@ def entity_locations( def entity_affiliates( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> GeneralEntityAffiliatesResponse: """ Get affiliated entities and subsidiaries for any entity. Returns parent companies, subsidiaries, and related entities with ownership information. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityAffiliatesResponse Successful response Examples @@ -181,54 +234,29 @@ def entity_affiliates( return _response.data def entity_news( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralEntityNewsResponse: """ Get recent news articles and mentions for any entity. Returns article titles, URLs, sources, publication dates, and snippets. Useful for tracking announcements, funding news, and company updates. Parameters ---------- entity_id : str + Entity ID (any type) - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.general.entity_news( - entity_id="openai", - ) - """ - _response = self._raw_client.entity_news(entity_id, request_options=request_options) - return _response.data - - def entity_updates( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of data updates for any entity. Returns history of changes to the entity's profile, tracking when information was added or modified. Useful for monitoring data freshness. - - Parameters - ---------- - entity_id : str + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityNewsResponse Successful response Examples @@ -239,11 +267,11 @@ def entity_updates( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.general.entity_updates( + client.general.entity_news( entity_id="openai", ) """ - _response = self._raw_client.entity_updates(entity_id, request_options=request_options) + _response = self._raw_client.entity_news(entity_id, limit=limit, request_options=request_options) return _response.data @@ -263,22 +291,37 @@ def with_raw_response(self) -> AsyncRawGeneralClient: return self._raw_client async def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: str, + entity_type: typing.Optional[GeneralSearchRequestEntityType] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralSearchResponse: """ - Search across all entity types (companies, people, investors, funds, deals, etc.) in a unified query. Returns matching entities with their type and basic information. Use this for broad discovery before drilling into specific entity types. + Cross-entity search across companies and people. + + Returns the company match first, then executives (C-suite) at that company, then other employees. + + Supports `limit` parameter to control results count (default: 25, max: 100). Parameters ---------- + q : str + Search query across all entity types (companies, people, investors) + + entity_type : typing.Optional[GeneralSearchRequestEntityType] + Filter by entity type + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralSearchResponse Successful response Examples @@ -294,28 +337,45 @@ async def search( async def main() -> None: - await client.general.search() + await client.general.search( + q="AI safety researchers at OpenAI", + limit=10, + ) asyncio.run(main()) """ - _response = await self._raw_client.search(limit=limit, request_options=request_options) + _response = await self._raw_client.search( + q=q, entity_type=entity_type, limit=limit, request_options=request_options + ) return _response.data async def search_shared( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralSearchSharedResponse: """ - Search for entities shared across your organization. Returns entities that multiple team members have accessed or tagged. Useful for discovering commonly referenced companies, investors, or people within your team. + Shared/saved search across entities. + + Same as cross-entity search with shared filter support. Returns companies and people matching the query. Parameters ---------- + q : typing.Optional[str] + Search query (optional) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralSearchSharedResponse Successful response Examples @@ -336,25 +396,37 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.search_shared(request_options=request_options) + _response = await self._raw_client.search_shared(q=q, limit=limit, request_options=request_options) return _response.data async def entity_people( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralEntityPeopleResponse: """ - Get people associated with any entity (company employees, fund team members, investor partners, etc.). Returns names, titles, and LinkedIn profiles for key people. + Get people associated with an entity (company employees/leadership). + + Returns executives first (C-suite, VP, Director), then other employees. Results are deduplicated. + + Supports `limit` parameter (default: 25, max: 100). Parameters ---------- entity_id : str + Entity ID (any type) + + limit : typing.Optional[int] + Maximum results to return (default: 25, max: 100) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityPeopleResponse Successful response Examples @@ -377,25 +449,26 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.entity_people(entity_id, request_options=request_options) + _response = await self._raw_client.entity_people(entity_id, limit=limit, request_options=request_options) return _response.data async def entity_locations( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> GeneralEntityLocationsResponse: """ Get office locations for any entity. Returns headquarters and branch office addresses with city, state, country, and full address details. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityLocationsResponse Successful response Examples @@ -423,20 +496,21 @@ async def main() -> None: async def entity_affiliates( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> GeneralEntityAffiliatesResponse: """ Get affiliated entities and subsidiaries for any entity. Returns parent companies, subsidiaries, and related entities with ownership information. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityAffiliatesResponse Successful response Examples @@ -463,62 +537,29 @@ async def main() -> None: return _response.data async def entity_news( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GeneralEntityNewsResponse: """ Get recent news articles and mentions for any entity. Returns article titles, URLs, sources, publication dates, and snippets. Useful for tracking announcements, funding news, and company updates. Parameters ---------- entity_id : str + Entity ID (any type) - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.general.entity_news( - entity_id="openai", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.entity_news(entity_id, request_options=request_options) - return _response.data - - async def entity_updates( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of data updates for any entity. Returns history of changes to the entity's profile, tracking when information was added or modified. Useful for monitoring data freshness. - - Parameters - ---------- - entity_id : str + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + GeneralEntityNewsResponse Successful response Examples @@ -534,12 +575,12 @@ async def entity_updates( async def main() -> None: - await client.general.entity_updates( + await client.general.entity_news( entity_id="openai", ) asyncio.run(main()) """ - _response = await self._raw_client.entity_updates(entity_id, request_options=request_options) + _response = await self._raw_client.entity_news(entity_id, limit=limit, request_options=request_options) return _response.data diff --git a/src/runcaptain/general/raw_client.py b/src/runcaptain/general/raw_client.py index 1fdcf48..5994de4 100644 --- a/src/runcaptain/general/raw_client.py +++ b/src/runcaptain/general/raw_client.py @@ -7,10 +7,19 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.unauthorized_error import UnauthorizedError +from .types.general_entity_affiliates_response import GeneralEntityAffiliatesResponse +from .types.general_entity_locations_response import GeneralEntityLocationsResponse +from .types.general_entity_news_response import GeneralEntityNewsResponse +from .types.general_entity_people_response import GeneralEntityPeopleResponse +from .types.general_search_request_entity_type import GeneralSearchRequestEntityType +from .types.general_search_response import GeneralSearchResponse +from .types.general_search_shared_response import GeneralSearchSharedResponse +from pydantic import ValidationError class RawGeneralClient: @@ -18,28 +27,45 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: str, + entity_type: typing.Optional[GeneralSearchRequestEntityType] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GeneralSearchResponse]: """ - Search across all entity types (companies, people, investors, funds, deals, etc.) in a unified query. Returns matching entities with their type and basic information. Use this for broad discovery before drilling into specific entity types. + Cross-entity search across companies and people. + + Returns the company match first, then executives (C-suite) at that company, then other employees. + + Supports `limit` parameter to control results count (default: 25, max: 100). Parameters ---------- + q : str + Search query across all entity types (companies, people, investors) + + entity_type : typing.Optional[GeneralSearchRequestEntityType] + Filter by entity type + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[GeneralSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/general/search", method="GET", params={ + "q": q, + "entity_type": entity_type, "limit": limit, }, request_options=request_options, @@ -47,9 +73,9 @@ def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -68,35 +94,55 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def search_shared( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GeneralSearchSharedResponse]: """ - Search for entities shared across your organization. Returns entities that multiple team members have accessed or tagged. Useful for discovering commonly referenced companies, investors, or people within your team. + Shared/saved search across entities. + + Same as cross-entity search with shared filter support. Returns companies and people matching the query. Parameters ---------- + q : typing.Optional[str] + Search query (optional) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[GeneralSearchSharedResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/general/search/shared", method="GET", + params={ + "q": q, + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralSearchSharedResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralSearchSharedResponse, # type: ignore object_=_response.json(), ), ) @@ -115,37 +161,56 @@ def search_shared( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def entity_people( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GeneralEntityPeopleResponse]: """ - Get people associated with any entity (company employees, fund team members, investor partners, etc.). Returns names, titles, and LinkedIn profiles for key people. + Get people associated with an entity (company employees/leadership). + + Returns executives first (C-suite, VP, Director), then other employees. Results are deduplicated. + + Supports `limit` parameter (default: 25, max: 100). Parameters ---------- entity_id : str + Entity ID (any type) + + limit : typing.Optional[int] + Maximum results to return (default: 25, max: 100) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[GeneralEntityPeopleResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/people", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityPeopleResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityPeopleResponse, # type: ignore object_=_response.json(), ), ) @@ -175,24 +240,29 @@ def entity_people( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def entity_locations( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[GeneralEntityLocationsResponse]: """ Get office locations for any entity. Returns headquarters and branch office addresses with city, state, country, and full address details. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[GeneralEntityLocationsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -203,9 +273,9 @@ def entity_locations( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityLocationsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityLocationsResponse, # type: ignore object_=_response.json(), ), ) @@ -235,24 +305,29 @@ def entity_locations( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def entity_affiliates( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[GeneralEntityAffiliatesResponse]: """ Get affiliated entities and subsidiaries for any entity. Returns parent companies, subsidiaries, and related entities with ownership information. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[GeneralEntityAffiliatesResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -263,9 +338,9 @@ def entity_affiliates( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityAffiliatesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityAffiliatesResponse, # type: ignore object_=_response.json(), ), ) @@ -295,97 +370,52 @@ def entity_affiliates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def entity_news( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GeneralEntityNewsResponse]: """ Get recent news articles and mentions for any entity. Returns article titles, URLs, sources, publication dates, and snippets. Useful for tracking announcements, funding news, and company updates. Parameters ---------- entity_id : str + Entity ID (any type) - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/news", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def entity_updates( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of data updates for any entity. Returns history of changes to the entity's profile, tracking when information was added or modified. Useful for monitoring data freshness. - - Parameters - ---------- - entity_id : str + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[GeneralEntityNewsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/updates", + f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/news", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityNewsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityNewsResponse, # type: ignore object_=_response.json(), ), ) @@ -415,6 +445,10 @@ def entity_updates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -423,28 +457,45 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: str, + entity_type: typing.Optional[GeneralSearchRequestEntityType] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GeneralSearchResponse]: """ - Search across all entity types (companies, people, investors, funds, deals, etc.) in a unified query. Returns matching entities with their type and basic information. Use this for broad discovery before drilling into specific entity types. + Cross-entity search across companies and people. + + Returns the company match first, then executives (C-suite) at that company, then other employees. + + Supports `limit` parameter to control results count (default: 25, max: 100). Parameters ---------- + q : str + Search query across all entity types (companies, people, investors) + + entity_type : typing.Optional[GeneralSearchRequestEntityType] + Filter by entity type + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[GeneralSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/general/search", method="GET", params={ + "q": q, + "entity_type": entity_type, "limit": limit, }, request_options=request_options, @@ -452,9 +503,9 @@ async def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -473,35 +524,55 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def search_shared( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GeneralSearchSharedResponse]: """ - Search for entities shared across your organization. Returns entities that multiple team members have accessed or tagged. Useful for discovering commonly referenced companies, investors, or people within your team. + Shared/saved search across entities. + + Same as cross-entity search with shared filter support. Returns companies and people matching the query. Parameters ---------- + q : typing.Optional[str] + Search query (optional) + + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[GeneralSearchSharedResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/general/search/shared", method="GET", + params={ + "q": q, + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralSearchSharedResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralSearchSharedResponse, # type: ignore object_=_response.json(), ), ) @@ -520,37 +591,56 @@ async def search_shared( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def entity_people( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GeneralEntityPeopleResponse]: """ - Get people associated with any entity (company employees, fund team members, investor partners, etc.). Returns names, titles, and LinkedIn profiles for key people. + Get people associated with an entity (company employees/leadership). + + Returns executives first (C-suite, VP, Director), then other employees. Results are deduplicated. + + Supports `limit` parameter (default: 25, max: 100). Parameters ---------- entity_id : str + Entity ID (any type) + + limit : typing.Optional[int] + Maximum results to return (default: 25, max: 100) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[GeneralEntityPeopleResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/people", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityPeopleResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityPeopleResponse, # type: ignore object_=_response.json(), ), ) @@ -580,24 +670,29 @@ async def entity_people( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def entity_locations( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[GeneralEntityLocationsResponse]: """ Get office locations for any entity. Returns headquarters and branch office addresses with city, state, country, and full address details. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[GeneralEntityLocationsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -608,9 +703,9 @@ async def entity_locations( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityLocationsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityLocationsResponse, # type: ignore object_=_response.json(), ), ) @@ -640,24 +735,29 @@ async def entity_locations( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def entity_affiliates( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[GeneralEntityAffiliatesResponse]: """ Get affiliated entities and subsidiaries for any entity. Returns parent companies, subsidiaries, and related entities with ownership information. Parameters ---------- entity_id : str + Entity ID (any type) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[GeneralEntityAffiliatesResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -668,9 +768,9 @@ async def entity_affiliates( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityAffiliatesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityAffiliatesResponse, # type: ignore object_=_response.json(), ), ) @@ -700,97 +800,52 @@ async def entity_affiliates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def entity_news( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + entity_id: str, + *, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GeneralEntityNewsResponse]: """ Get recent news articles and mentions for any entity. Returns article titles, URLs, sources, publication dates, and snippets. Useful for tracking announcements, funding news, and company updates. Parameters ---------- entity_id : str + Entity ID (any type) - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/news", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def entity_updates( - self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of data updates for any entity. Returns history of changes to the entity's profile, tracking when information was added or modified. Useful for monitoring data freshness. - - Parameters - ---------- - entity_id : str + limit : typing.Optional[int] + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[GeneralEntityNewsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/updates", + f"v2/datasets/odyssey/general/{jsonable_encoder(entity_id)}/news", method="GET", + params={ + "limit": limit, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + GeneralEntityNewsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=GeneralEntityNewsResponse, # type: ignore object_=_response.json(), ), ) @@ -820,4 +875,8 @@ async def entity_updates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/general/types/__init__.py b/src/runcaptain/general/types/__init__.py new file mode 100644 index 0000000..e22b751 --- /dev/null +++ b/src/runcaptain/general/types/__init__.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .general_entity_affiliates_response import GeneralEntityAffiliatesResponse + from .general_entity_affiliates_response_affiliates_item import GeneralEntityAffiliatesResponseAffiliatesItem + from .general_entity_locations_response import GeneralEntityLocationsResponse + from .general_entity_locations_response_locations_item import GeneralEntityLocationsResponseLocationsItem + from .general_entity_news_response import GeneralEntityNewsResponse + from .general_entity_news_response_news_item import GeneralEntityNewsResponseNewsItem + from .general_entity_people_response import GeneralEntityPeopleResponse + from .general_entity_people_response_people_item import GeneralEntityPeopleResponsePeopleItem + from .general_search_request_entity_type import GeneralSearchRequestEntityType + from .general_search_response import GeneralSearchResponse + from .general_search_response_results_item import GeneralSearchResponseResultsItem + from .general_search_shared_response import GeneralSearchSharedResponse + from .general_search_shared_response_results_item import GeneralSearchSharedResponseResultsItem +_dynamic_imports: typing.Dict[str, str] = { + "GeneralEntityAffiliatesResponse": ".general_entity_affiliates_response", + "GeneralEntityAffiliatesResponseAffiliatesItem": ".general_entity_affiliates_response_affiliates_item", + "GeneralEntityLocationsResponse": ".general_entity_locations_response", + "GeneralEntityLocationsResponseLocationsItem": ".general_entity_locations_response_locations_item", + "GeneralEntityNewsResponse": ".general_entity_news_response", + "GeneralEntityNewsResponseNewsItem": ".general_entity_news_response_news_item", + "GeneralEntityPeopleResponse": ".general_entity_people_response", + "GeneralEntityPeopleResponsePeopleItem": ".general_entity_people_response_people_item", + "GeneralSearchRequestEntityType": ".general_search_request_entity_type", + "GeneralSearchResponse": ".general_search_response", + "GeneralSearchResponseResultsItem": ".general_search_response_results_item", + "GeneralSearchSharedResponse": ".general_search_shared_response", + "GeneralSearchSharedResponseResultsItem": ".general_search_shared_response_results_item", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GeneralEntityAffiliatesResponse", + "GeneralEntityAffiliatesResponseAffiliatesItem", + "GeneralEntityLocationsResponse", + "GeneralEntityLocationsResponseLocationsItem", + "GeneralEntityNewsResponse", + "GeneralEntityNewsResponseNewsItem", + "GeneralEntityPeopleResponse", + "GeneralEntityPeopleResponsePeopleItem", + "GeneralSearchRequestEntityType", + "GeneralSearchResponse", + "GeneralSearchResponseResultsItem", + "GeneralSearchSharedResponse", + "GeneralSearchSharedResponseResultsItem", +] diff --git a/src/runcaptain/general/types/general_entity_affiliates_response.py b/src/runcaptain/general/types/general_entity_affiliates_response.py new file mode 100644 index 0000000..a7aa23d --- /dev/null +++ b/src/runcaptain/general/types/general_entity_affiliates_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .general_entity_affiliates_response_affiliates_item import GeneralEntityAffiliatesResponseAffiliatesItem + + +class GeneralEntityAffiliatesResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity identifier + """ + + affiliates: typing.Optional[typing.List[GeneralEntityAffiliatesResponseAffiliatesItem]] = None + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of affiliates + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_affiliates_response_affiliates_item.py b/src/runcaptain/general/types/general_entity_affiliates_response_affiliates_item.py new file mode 100644 index 0000000..89ad1bf --- /dev/null +++ b/src/runcaptain/general/types/general_entity_affiliates_response_affiliates_item.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GeneralEntityAffiliatesResponseAffiliatesItem(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Affiliate entity ID + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Affiliate name + """ + + relationship: typing.Optional[str] = pydantic.Field(default=None) + """ + Relationship type (parent, subsidiary, partner) + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_locations_response.py b/src/runcaptain/general/types/general_entity_locations_response.py new file mode 100644 index 0000000..d7bca25 --- /dev/null +++ b/src/runcaptain/general/types/general_entity_locations_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .general_entity_locations_response_locations_item import GeneralEntityLocationsResponseLocationsItem + + +class GeneralEntityLocationsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity identifier + """ + + locations: typing.Optional[typing.List[GeneralEntityLocationsResponseLocationsItem]] = None + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of locations + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_locations_response_locations_item.py b/src/runcaptain/general/types/general_entity_locations_response_locations_item.py new file mode 100644 index 0000000..181138a --- /dev/null +++ b/src/runcaptain/general/types/general_entity_locations_response_locations_item.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GeneralEntityLocationsResponseLocationsItem(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Location name + """ + + city: typing.Optional[str] = pydantic.Field(default=None) + """ + City + """ + + state: typing.Optional[str] = pydantic.Field(default=None) + """ + State/region + """ + + country: typing.Optional[str] = pydantic.Field(default=None) + """ + Country + """ + + is_headquarters: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether this is the HQ + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_news_response.py b/src/runcaptain/general/types/general_entity_news_response.py new file mode 100644 index 0000000..e1c07c1 --- /dev/null +++ b/src/runcaptain/general/types/general_entity_news_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .general_entity_news_response_news_item import GeneralEntityNewsResponseNewsItem + + +class GeneralEntityNewsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity identifier + """ + + news: typing.Optional[typing.List[GeneralEntityNewsResponseNewsItem]] = None + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of articles + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_news_response_news_item.py b/src/runcaptain/general/types/general_entity_news_response_news_item.py new file mode 100644 index 0000000..fc5d9df --- /dev/null +++ b/src/runcaptain/general/types/general_entity_news_response_news_item.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GeneralEntityNewsResponseNewsItem(UniversalBaseModel): + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Article title + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Article URL + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + News source + """ + + published_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Publication date (YYYY-MM-DD) + """ + + snippet: typing.Optional[str] = pydantic.Field(default=None) + """ + Article excerpt + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_people_response.py b/src/runcaptain/general/types/general_entity_people_response.py new file mode 100644 index 0000000..153d723 --- /dev/null +++ b/src/runcaptain/general/types/general_entity_people_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .general_entity_people_response_people_item import GeneralEntityPeopleResponsePeopleItem + + +class GeneralEntityPeopleResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity identifier + """ + + people: typing.Optional[typing.List[GeneralEntityPeopleResponsePeopleItem]] = None + total_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of people + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_entity_people_response_people_item.py b/src/runcaptain/general/types/general_entity_people_response_people_item.py new file mode 100644 index 0000000..7aca5e5 --- /dev/null +++ b/src/runcaptain/general/types/general_entity_people_response_people_item.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GeneralEntityPeopleResponsePeopleItem(UniversalBaseModel): + full_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Full name + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Job title + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn URL + """ + + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Person entity ID + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_search_request_entity_type.py b/src/runcaptain/general/types/general_search_request_entity_type.py new file mode 100644 index 0000000..ba29293 --- /dev/null +++ b/src/runcaptain/general/types/general_search_request_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GeneralSearchRequestEntityType = typing.Union[ + typing.Literal["company", "person", "investor", "fund", "deal", "lp", "service_provider"], typing.Any +] diff --git a/src/runcaptain/general/types/general_search_response.py b/src/runcaptain/general/types/general_search_response.py new file mode 100644 index 0000000..27a364d --- /dev/null +++ b/src/runcaptain/general/types/general_search_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .general_search_response_results_item import GeneralSearchResponseResultsItem + + +class GeneralSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[GeneralSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum results requested + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_search_response_results_item.py b/src/runcaptain/general/types/general_search_response_results_item.py new file mode 100644 index 0000000..de53d3e --- /dev/null +++ b/src/runcaptain/general/types/general_search_response_results_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GeneralSearchResponseResultsItem(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity type (company, person, investor) + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Brief description + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_search_shared_response.py b/src/runcaptain/general/types/general_search_shared_response.py new file mode 100644 index 0000000..5d9fb18 --- /dev/null +++ b/src/runcaptain/general/types/general_search_shared_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .general_search_shared_response_results_item import GeneralSearchSharedResponseResultsItem + + +class GeneralSearchSharedResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[GeneralSearchSharedResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum results requested + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/general/types/general_search_shared_response_results_item.py b/src/runcaptain/general/types/general_search_shared_response_results_item.py new file mode 100644 index 0000000..73a6702 --- /dev/null +++ b/src/runcaptain/general/types/general_search_shared_response_results_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GeneralSearchSharedResponseResultsItem(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity type (company, person, investor) + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Brief description + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/indexing/__init__.py b/src/runcaptain/indexing/__init__.py index 2c72af1..04973b9 100644 --- a/src/runcaptain/indexing/__init__.py +++ b/src/runcaptain/indexing/__init__.py @@ -10,6 +10,7 @@ IndexAzureDirectoryRequestV2ProcessingType, IndexAzureFileRequestV2ProcessingType, IndexAzureRequestV2ProcessingType, + IndexFileV2RequestProcessingType, IndexGcsDirectoryRequestV2ProcessingType, IndexGcsFileRequestV2ProcessingType, IndexGcsRequestV2ProcessingType, @@ -28,6 +29,7 @@ "IndexAzureDirectoryRequestV2ProcessingType": ".types", "IndexAzureFileRequestV2ProcessingType": ".types", "IndexAzureRequestV2ProcessingType": ".types", + "IndexFileV2RequestProcessingType": ".types", "IndexGcsDirectoryRequestV2ProcessingType": ".types", "IndexGcsFileRequestV2ProcessingType": ".types", "IndexGcsRequestV2ProcessingType": ".types", @@ -69,6 +71,7 @@ def __dir__(): "IndexAzureDirectoryRequestV2ProcessingType", "IndexAzureFileRequestV2ProcessingType", "IndexAzureRequestV2ProcessingType", + "IndexFileV2RequestProcessingType", "IndexGcsDirectoryRequestV2ProcessingType", "IndexGcsFileRequestV2ProcessingType", "IndexGcsRequestV2ProcessingType", diff --git a/src/runcaptain/indexing/client.py b/src/runcaptain/indexing/client.py index b0824bd..bf977e6 100644 --- a/src/runcaptain/indexing/client.py +++ b/src/runcaptain/indexing/client.py @@ -2,13 +2,16 @@ import typing +from .. import core from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from ..types.index_job_response_v2 import IndexJobResponseV2 +from ..types.validate_parsing_script_response_v2 import ValidateParsingScriptResponseV2 from .raw_client import AsyncRawIndexingClient, RawIndexingClient from .types.index_azure_directory_request_v2processing_type import IndexAzureDirectoryRequestV2ProcessingType from .types.index_azure_file_request_v2processing_type import IndexAzureFileRequestV2ProcessingType from .types.index_azure_request_v2processing_type import IndexAzureRequestV2ProcessingType +from .types.index_file_v2request_processing_type import IndexFileV2RequestProcessingType from .types.index_gcs_directory_request_v2processing_type import IndexGcsDirectoryRequestV2ProcessingType from .types.index_gcs_file_request_v2processing_type import IndexGcsFileRequestV2ProcessingType from .types.index_gcs_request_v2processing_type import IndexGcsRequestV2ProcessingType @@ -50,10 +53,12 @@ def index_s3bucket_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -62,6 +67,7 @@ def index_s3bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -75,6 +81,9 @@ def index_s3bucket_v2( processing_type : IndexS3RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -87,6 +96,9 @@ def index_s3bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -119,10 +131,12 @@ def index_s3bucket_v2( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, bucket_region=bucket_region, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -138,6 +152,7 @@ def index_s3file_v2( processing_type: IndexS3FileRequestV2ProcessingType, bucket_region: typing.Optional[str] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -146,6 +161,7 @@ def index_s3file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -168,6 +184,9 @@ def index_s3file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -203,6 +222,7 @@ def index_s3file_v2( processing_type=processing_type, bucket_region=bucket_region, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -217,6 +237,7 @@ def index_gcs_bucket_v2( max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -225,6 +246,7 @@ def index_gcs_bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -244,6 +266,9 @@ def index_gcs_bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -275,6 +300,7 @@ def index_gcs_bucket_v2( max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -288,6 +314,7 @@ def index_gcs_file_v2( service_account_json: str, processing_type: IndexGcsFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -296,6 +323,7 @@ def index_gcs_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -312,6 +340,9 @@ def index_gcs_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -343,6 +374,7 @@ def index_gcs_file_v2( service_account_json=service_account_json, processing_type=processing_type, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -356,10 +388,12 @@ def index_s3directory_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -368,6 +402,7 @@ def index_s3directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -384,6 +419,9 @@ def index_s3directory_v2( processing_type : IndexS3DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -396,6 +434,9 @@ def index_s3directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -429,10 +470,12 @@ def index_s3directory_v2( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, bucket_region=bucket_region, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -445,9 +488,11 @@ def index_gcs_directory_v2( directory_path: str, service_account_json: str, processing_type: IndexGcsDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -456,6 +501,7 @@ def index_gcs_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -469,6 +515,9 @@ def index_gcs_directory_v2( processing_type : IndexGcsDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -478,6 +527,9 @@ def index_gcs_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -508,9 +560,11 @@ def index_gcs_directory_v2( directory_path=directory_path, service_account_json=service_account_json, processing_type=processing_type, + idempotency_key=idempotency_key, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -523,9 +577,11 @@ def index_azure_container_v2( account_name: str, account_key: str, processing_type: IndexAzureRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -534,6 +590,7 @@ def index_azure_container_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -547,6 +604,9 @@ def index_azure_container_v2( processing_type : IndexAzureRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -556,6 +616,9 @@ def index_azure_container_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -586,9 +649,11 @@ def index_azure_container_v2( account_name=account_name, account_key=account_key, processing_type=processing_type, + idempotency_key=idempotency_key, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -603,6 +668,7 @@ def index_azure_file_v2( account_key: str, processing_type: IndexAzureFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -611,6 +677,7 @@ def index_azure_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -630,6 +697,9 @@ def index_azure_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -663,6 +733,7 @@ def index_azure_file_v2( account_key=account_key, processing_type=processing_type, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -676,9 +747,11 @@ def index_azure_directory_v2( account_name: str, account_key: str, processing_type: IndexAzureDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -687,6 +760,7 @@ def index_azure_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -703,6 +777,9 @@ def index_azure_directory_v2( processing_type : IndexAzureDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -712,6 +789,9 @@ def index_azure_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -744,9 +824,11 @@ def index_azure_directory_v2( account_name=account_name, account_key=account_key, processing_type=processing_type, + idempotency_key=idempotency_key, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -760,18 +842,21 @@ def index_r2bucket_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2RequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ - Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible — provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible - provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -788,6 +873,9 @@ def index_r2bucket_v2( processing_type : IndexR2RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2RequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -800,6 +888,9 @@ def index_r2bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -832,10 +923,12 @@ def index_r2bucket_v2( access_key_id=access_key_id, secret_access_key=secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, jurisdiction=jurisdiction, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -852,6 +945,7 @@ def index_r2file_v2( processing_type: IndexR2FileRequestV2ProcessingType, jurisdiction: typing.Optional[IndexR2FileRequestV2Jurisdiction] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -860,6 +954,7 @@ def index_r2file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -885,6 +980,9 @@ def index_r2file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -921,6 +1019,7 @@ def index_r2file_v2( processing_type=processing_type, jurisdiction=jurisdiction, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -935,10 +1034,12 @@ def index_r2directory_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -947,6 +1048,7 @@ def index_r2directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -966,6 +1068,9 @@ def index_r2directory_v2( processing_type : IndexR2DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -978,6 +1083,9 @@ def index_r2directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1012,10 +1120,12 @@ def index_r2directory_v2( access_key_id=access_key_id, secret_access_key=secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, jurisdiction=jurisdiction, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1025,38 +1135,63 @@ def index_url_v2( collection_name: str, *, processing_type: IndexUrlRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, url: typing.Optional[str] = OMIT, urls: typing.Optional[typing.Sequence[str]] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ - Index documents from public URLs into a collection. No cloud storage credentials required. + Index documents or web pages from public URLs into a collection. No cloud storage credentials required. You can provide either: - - `url` — a single URL string for one document - - `urls` — an array of URL strings for multiple documents + - `url` - a single URL string + - `urls` - an array of URL strings + + ## Smart Content Detection + + The endpoint automatically detects whether a URL points to a hosted file or a web page: - Supported file types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Documents are downloaded and processed through the same pipeline as cloud storage indexing. + - **Hosted files** (PDF, DOCX, XLSX, CSV, TXT, images, etc.) are downloaded and processed directly through the indexing pipeline. + - **Web pages** (HTML) are automatically scraped - text content is extracted as markdown and page images are downloaded and indexed. Bot-protected pages are handled via web unlocker technology. + + ## Supported Content + + - **Documents**: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML + - **Images**: PNG, JPG, JPEG, GIF, BMP, TIFF + - **Web pages**: Any public URL serving HTML - text and images are extracted automatically + + ## Processing Modes for Web Pages + + - **advanced**: Extracts text content as markdown AND downloads and indexes all page images + - **basic**: Extracts text content only - faster and lower cost Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into processing_type : IndexUrlRequestV2ProcessingType - Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + Processing mode. For hosted documents: 'advanced' enables AI-enhanced extraction for complex layouts, tables, figures, and charts; 'basic' provides standard document processing. For web pages: 'advanced' extracts both text content and page images; 'basic' extracts text content only (faster, lower cost). + + idempotency_key : typing.Optional[str] + UUID for request deduplication url : typing.Optional[str] - A single public URL to a hosted document. Supported types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Provide either 'url' or 'urls', not both. + A single public URL to a document or web page. Hosted files (PDF, DOCX, etc.) are indexed directly. Web pages (HTML) are automatically scraped - text and images are extracted. Provide either 'url' or 'urls', not both. urls : typing.Optional[typing.Sequence[str]] - An array of public URLs to hosted documents. Provide either 'url' or 'urls', not both. + An array of public URLs to documents or web pages. Each URL is auto-detected - hosted files are indexed directly, web pages are scraped. Provide either 'url' or 'urls', not both. custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1082,13 +1217,279 @@ def index_url_v2( _response = self._raw_client.index_url_v2( collection_name, processing_type=processing_type, + idempotency_key=idempotency_key, + url=url, + urls=urls, + custom_metadata=custom_metadata, + parsing_script=parsing_script, + request_options=request_options, + ) + return _response.data + + def index_youtube_v2( + self, + collection_name: str, + *, + idempotency_key: typing.Optional[str] = None, + url: typing.Optional[str] = OMIT, + urls: typing.Optional[typing.Sequence[str]] = OMIT, + languages: typing.Optional[typing.Sequence[str]] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> IndexJobResponseV2: + """ + Index YouTube video transcripts into a collection. + + Fetches transcripts from YouTube videos using auto-generated or manual captions, formats them with inline timestamps, and indexes the text for semantic search. + + You can provide either: + - `url` - a single YouTube video URL + - `urls` - an array of YouTube video URLs (max 20) + + Transcripts are always processed as basic text (no OCR needed). Each transcript is formatted with `[HH:MM:SS]` timestamp markers so search results can reference specific moments in the video. + + ## Supported URL Formats + - `youtube.com/watch?v=VIDEO_ID` + - `youtu.be/VIDEO_ID` + - `youtube.com/shorts/VIDEO_ID` + + ## Auto-Injected Metadata + The following metadata is automatically added to indexed chunks: + - `youtube_video_id` - the video ID + - `youtube_url` - the original video URL + - `youtube_language` - transcript language + - `youtube_duration_seconds` - total video duration + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + url : typing.Optional[str] + A single YouTube video URL. Supported formats: youtube.com/watch?v=, youtu.be/, youtube.com/shorts/. Provide either 'url' or 'urls', not both. + + urls : typing.Optional[typing.Sequence[str]] + An array of YouTube video URLs to index (max 20). Provide either 'url' or 'urls', not both. + + languages : typing.Optional[typing.Sequence[str]] + Preferred transcript languages in priority order (ISO 639-1 codes). Defaults to English. Only specify if you need a non-English transcript (e.g., ['fr', 'de']). Falls back to auto-generated captions if manual transcript unavailable. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + IndexJobResponseV2 + Indexing job started + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.indexing.index_youtube_v2( + collection_name="my_collection", + url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", + ) + """ + _response = self._raw_client.index_youtube_v2( + collection_name, + idempotency_key=idempotency_key, url=url, urls=urls, + languages=languages, + custom_metadata=custom_metadata, + request_options=request_options, + ) + return _response.data + + def index_text_v2( + self, + collection_name: str, + *, + content: str, + idempotency_key: typing.Optional[str] = None, + filename: typing.Optional[str] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> IndexJobResponseV2: + """ + Index plain text content into a collection. + + Accepts raw text content in the request body, saves it as a document, and indexes it for semantic search. No file upload or cloud storage needed. + + Text is always processed as basic (no OCR). Ideal for indexing scraped content, notes, articles, or any plain text data. + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + content : str + The text content to index. + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + filename : typing.Optional[str] + Optional filename for the text document. Defaults to 'snippet-{N}.txt' where N auto-increments. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + IndexJobResponseV2 + Indexing job started + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.indexing.index_text_v2( + collection_name="my_collection", + content="Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience...", + filename="ml_notes.txt", + ) + """ + _response = self._raw_client.index_text_v2( + collection_name, + content=content, + idempotency_key=idempotency_key, + filename=filename, + custom_metadata=custom_metadata, + request_options=request_options, + ) + return _response.data + + def index_file_v2( + self, + collection_name: str, + *, + files: typing.List[core.File], + idempotency_key: typing.Optional[str] = None, + processing_type: typing.Optional[IndexFileV2RequestProcessingType] = OMIT, + custom_metadata: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> IndexJobResponseV2: + """ + Upload and index files directly into a collection via multipart form-data. + + Upload one or more files (max 20) in a single request. Supports PDF, DOCX, XLSX, CSV, TXT, images, and other document types. Files are processed through the same pipeline as cloud storage indexing. + + ## Supported File Types + PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF + + ## Size Limits + - Maximum 100MB per file + - Maximum 20 files per request + + ## Processing Modes + - **advanced**: AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images (2.5 credits/page) + - **basic**: Standard document processing optimized for general indexing (1 credit/page) + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + files : typing.List[core.File] + See core.File for more documentation + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + processing_type : typing.Optional[IndexFileV2RequestProcessingType] + Document processing type: 'advanced' for AI-enhanced extraction, 'basic' for standard processing + + custom_metadata : typing.Optional[str] + JSON string of custom metadata to attach to all indexed chunks + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + IndexJobResponseV2 + Indexing job started + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.indexing.index_file_v2( + collection_name="my_collection", + processing_type="advanced", + ) + """ + _response = self._raw_client.index_file_v2( + collection_name, + files=files, + idempotency_key=idempotency_key, + processing_type=processing_type, custom_metadata=custom_metadata, request_options=request_options, ) return _response.data + def validate_parsing_script_v2( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> ValidateParsingScriptResponseV2: + """ + Validates a JavaScript parsing script without running it against real data. Upload your .js file as multipart/form-data under the file field. Checks that the script parses cleanly and exports a default function. Use this before uploading a script to catch syntax errors and structural problems. The return-type contract (must return a string) is enforced at indexing time by json_handler against your real JSON. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ValidateParsingScriptResponseV2 + Validation result. Returned for both valid AND invalid scripts. + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.indexing.validate_parsing_script_v2() + """ + _response = self._raw_client.validate_parsing_script_v2(file=file, request_options=request_options) + return _response.data + class AsyncIndexingClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -1113,10 +1514,12 @@ async def index_s3bucket_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1125,6 +1528,7 @@ async def index_s3bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -1138,6 +1542,9 @@ async def index_s3bucket_v2( processing_type : IndexS3RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -1150,6 +1557,9 @@ async def index_s3bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1190,10 +1600,12 @@ async def main() -> None: aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, bucket_region=bucket_region, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1209,6 +1621,7 @@ async def index_s3file_v2( processing_type: IndexS3FileRequestV2ProcessingType, bucket_region: typing.Optional[str] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1217,6 +1630,7 @@ async def index_s3file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -1239,6 +1653,9 @@ async def index_s3file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1282,6 +1699,7 @@ async def main() -> None: processing_type=processing_type, bucket_region=bucket_region, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1296,6 +1714,7 @@ async def index_gcs_bucket_v2( max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1304,6 +1723,7 @@ async def index_gcs_bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -1323,6 +1743,9 @@ async def index_gcs_bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1362,6 +1785,7 @@ async def main() -> None: max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1375,6 +1799,7 @@ async def index_gcs_file_v2( service_account_json: str, processing_type: IndexGcsFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1383,6 +1808,7 @@ async def index_gcs_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -1399,6 +1825,9 @@ async def index_gcs_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1438,6 +1867,7 @@ async def main() -> None: service_account_json=service_account_json, processing_type=processing_type, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1451,10 +1881,12 @@ async def index_s3directory_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1463,6 +1895,7 @@ async def index_s3directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -1479,6 +1912,9 @@ async def index_s3directory_v2( processing_type : IndexS3DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -1491,6 +1927,9 @@ async def index_s3directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1532,10 +1971,12 @@ async def main() -> None: aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, bucket_region=bucket_region, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1548,9 +1989,11 @@ async def index_gcs_directory_v2( directory_path: str, service_account_json: str, processing_type: IndexGcsDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1559,6 +2002,7 @@ async def index_gcs_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -1572,6 +2016,9 @@ async def index_gcs_directory_v2( processing_type : IndexGcsDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -1581,6 +2028,9 @@ async def index_gcs_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1619,9 +2069,11 @@ async def main() -> None: directory_path=directory_path, service_account_json=service_account_json, processing_type=processing_type, + idempotency_key=idempotency_key, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1634,9 +2086,11 @@ async def index_azure_container_v2( account_name: str, account_key: str, processing_type: IndexAzureRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1645,6 +2099,7 @@ async def index_azure_container_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -1658,6 +2113,9 @@ async def index_azure_container_v2( processing_type : IndexAzureRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -1667,6 +2125,9 @@ async def index_azure_container_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1705,9 +2166,11 @@ async def main() -> None: account_name=account_name, account_key=account_key, processing_type=processing_type, + idempotency_key=idempotency_key, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1722,6 +2185,7 @@ async def index_azure_file_v2( account_key: str, processing_type: IndexAzureFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1730,6 +2194,7 @@ async def index_azure_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -1749,6 +2214,9 @@ async def index_azure_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1790,6 +2258,7 @@ async def main() -> None: account_key=account_key, processing_type=processing_type, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1803,9 +2272,11 @@ async def index_azure_directory_v2( account_name: str, account_key: str, processing_type: IndexAzureDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -1814,6 +2285,7 @@ async def index_azure_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -1830,6 +2302,9 @@ async def index_azure_directory_v2( processing_type : IndexAzureDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -1839,6 +2314,9 @@ async def index_azure_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1879,9 +2357,11 @@ async def main() -> None: account_name=account_name, account_key=account_key, processing_type=processing_type, + idempotency_key=idempotency_key, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1895,18 +2375,21 @@ async def index_r2bucket_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2RequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ - Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible — provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible - provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -1923,6 +2406,9 @@ async def index_r2bucket_v2( processing_type : IndexR2RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2RequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -1935,6 +2421,9 @@ async def index_r2bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1975,10 +2464,12 @@ async def main() -> None: access_key_id=access_key_id, secret_access_key=secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, jurisdiction=jurisdiction, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -1995,6 +2486,7 @@ async def index_r2file_v2( processing_type: IndexR2FileRequestV2ProcessingType, jurisdiction: typing.Optional[IndexR2FileRequestV2Jurisdiction] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -2003,6 +2495,7 @@ async def index_r2file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -2028,6 +2521,9 @@ async def index_r2file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2072,6 +2568,7 @@ async def main() -> None: processing_type=processing_type, jurisdiction=jurisdiction, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -2086,10 +2583,12 @@ async def index_r2directory_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ @@ -2098,6 +2597,7 @@ async def index_r2directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -2117,6 +2617,9 @@ async def index_r2directory_v2( processing_type : IndexR2DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -2129,6 +2632,9 @@ async def index_r2directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2171,10 +2677,12 @@ async def main() -> None: access_key_id=access_key_id, secret_access_key=secret_access_key, processing_type=processing_type, + idempotency_key=idempotency_key, jurisdiction=jurisdiction, max_files=max_files, skip_existing=skip_existing, custom_metadata=custom_metadata, + parsing_script=parsing_script, request_options=request_options, ) return _response.data @@ -2184,38 +2692,63 @@ async def index_url_v2( collection_name: str, *, processing_type: IndexUrlRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, url: typing.Optional[str] = OMIT, urls: typing.Optional[typing.Sequence[str]] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> IndexJobResponseV2: """ - Index documents from public URLs into a collection. No cloud storage credentials required. + Index documents or web pages from public URLs into a collection. No cloud storage credentials required. You can provide either: - - `url` — a single URL string for one document - - `urls` — an array of URL strings for multiple documents + - `url` - a single URL string + - `urls` - an array of URL strings + + ## Smart Content Detection + + The endpoint automatically detects whether a URL points to a hosted file or a web page: + + - **Hosted files** (PDF, DOCX, XLSX, CSV, TXT, images, etc.) are downloaded and processed directly through the indexing pipeline. + - **Web pages** (HTML) are automatically scraped - text content is extracted as markdown and page images are downloaded and indexed. Bot-protected pages are handled via web unlocker technology. + + ## Supported Content - Supported file types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Documents are downloaded and processed through the same pipeline as cloud storage indexing. + - **Documents**: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML + - **Images**: PNG, JPG, JPEG, GIF, BMP, TIFF + - **Web pages**: Any public URL serving HTML - text and images are extracted automatically + + ## Processing Modes for Web Pages + + - **advanced**: Extracts text content as markdown AND downloads and indexes all page images + - **basic**: Extracts text content only - faster and lower cost Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into processing_type : IndexUrlRequestV2ProcessingType - Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + Processing mode. For hosted documents: 'advanced' enables AI-enhanced extraction for complex layouts, tables, figures, and charts; 'basic' provides standard document processing. For web pages: 'advanced' extracts both text content and page images; 'basic' extracts text content only (faster, lower cost). + + idempotency_key : typing.Optional[str] + UUID for request deduplication url : typing.Optional[str] - A single public URL to a hosted document. Supported types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Provide either 'url' or 'urls', not both. + A single public URL to a document or web page. Hosted files (PDF, DOCX, etc.) are indexed directly. Web pages (HTML) are automatically scraped - text and images are extracted. Provide either 'url' or 'urls', not both. urls : typing.Optional[typing.Sequence[str]] - An array of public URLs to hosted documents. Provide either 'url' or 'urls', not both. + An array of public URLs to documents or web pages. Each URL is auto-detected - hosted files are indexed directly, web pages are scraped. Provide either 'url' or 'urls', not both. custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2249,9 +2782,307 @@ async def main() -> None: _response = await self._raw_client.index_url_v2( collection_name, processing_type=processing_type, + idempotency_key=idempotency_key, + url=url, + urls=urls, + custom_metadata=custom_metadata, + parsing_script=parsing_script, + request_options=request_options, + ) + return _response.data + + async def index_youtube_v2( + self, + collection_name: str, + *, + idempotency_key: typing.Optional[str] = None, + url: typing.Optional[str] = OMIT, + urls: typing.Optional[typing.Sequence[str]] = OMIT, + languages: typing.Optional[typing.Sequence[str]] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> IndexJobResponseV2: + """ + Index YouTube video transcripts into a collection. + + Fetches transcripts from YouTube videos using auto-generated or manual captions, formats them with inline timestamps, and indexes the text for semantic search. + + You can provide either: + - `url` - a single YouTube video URL + - `urls` - an array of YouTube video URLs (max 20) + + Transcripts are always processed as basic text (no OCR needed). Each transcript is formatted with `[HH:MM:SS]` timestamp markers so search results can reference specific moments in the video. + + ## Supported URL Formats + - `youtube.com/watch?v=VIDEO_ID` + - `youtu.be/VIDEO_ID` + - `youtube.com/shorts/VIDEO_ID` + + ## Auto-Injected Metadata + The following metadata is automatically added to indexed chunks: + - `youtube_video_id` - the video ID + - `youtube_url` - the original video URL + - `youtube_language` - transcript language + - `youtube_duration_seconds` - total video duration + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + url : typing.Optional[str] + A single YouTube video URL. Supported formats: youtube.com/watch?v=, youtu.be/, youtube.com/shorts/. Provide either 'url' or 'urls', not both. + + urls : typing.Optional[typing.Sequence[str]] + An array of YouTube video URLs to index (max 20). Provide either 'url' or 'urls', not both. + + languages : typing.Optional[typing.Sequence[str]] + Preferred transcript languages in priority order (ISO 639-1 codes). Defaults to English. Only specify if you need a non-English transcript (e.g., ['fr', 'de']). Falls back to auto-generated captions if manual transcript unavailable. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + IndexJobResponseV2 + Indexing job started + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.indexing.index_youtube_v2( + collection_name="my_collection", + url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.index_youtube_v2( + collection_name, + idempotency_key=idempotency_key, url=url, urls=urls, + languages=languages, + custom_metadata=custom_metadata, + request_options=request_options, + ) + return _response.data + + async def index_text_v2( + self, + collection_name: str, + *, + content: str, + idempotency_key: typing.Optional[str] = None, + filename: typing.Optional[str] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> IndexJobResponseV2: + """ + Index plain text content into a collection. + + Accepts raw text content in the request body, saves it as a document, and indexes it for semantic search. No file upload or cloud storage needed. + + Text is always processed as basic (no OCR). Ideal for indexing scraped content, notes, articles, or any plain text data. + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + content : str + The text content to index. + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + filename : typing.Optional[str] + Optional filename for the text document. Defaults to 'snippet-{N}.txt' where N auto-increments. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + IndexJobResponseV2 + Indexing job started + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.indexing.index_text_v2( + collection_name="my_collection", + content="Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience...", + filename="ml_notes.txt", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.index_text_v2( + collection_name, + content=content, + idempotency_key=idempotency_key, + filename=filename, + custom_metadata=custom_metadata, + request_options=request_options, + ) + return _response.data + + async def index_file_v2( + self, + collection_name: str, + *, + files: typing.List[core.File], + idempotency_key: typing.Optional[str] = None, + processing_type: typing.Optional[IndexFileV2RequestProcessingType] = OMIT, + custom_metadata: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> IndexJobResponseV2: + """ + Upload and index files directly into a collection via multipart form-data. + + Upload one or more files (max 20) in a single request. Supports PDF, DOCX, XLSX, CSV, TXT, images, and other document types. Files are processed through the same pipeline as cloud storage indexing. + + ## Supported File Types + PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF + + ## Size Limits + - Maximum 100MB per file + - Maximum 20 files per request + + ## Processing Modes + - **advanced**: AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images (2.5 credits/page) + - **basic**: Standard document processing optimized for general indexing (1 credit/page) + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + files : typing.List[core.File] + See core.File for more documentation + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + processing_type : typing.Optional[IndexFileV2RequestProcessingType] + Document processing type: 'advanced' for AI-enhanced extraction, 'basic' for standard processing + + custom_metadata : typing.Optional[str] + JSON string of custom metadata to attach to all indexed chunks + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + IndexJobResponseV2 + Indexing job started + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.indexing.index_file_v2( + collection_name="my_collection", + processing_type="advanced", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.index_file_v2( + collection_name, + files=files, + idempotency_key=idempotency_key, + processing_type=processing_type, custom_metadata=custom_metadata, request_options=request_options, ) return _response.data + + async def validate_parsing_script_v2( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> ValidateParsingScriptResponseV2: + """ + Validates a JavaScript parsing script without running it against real data. Upload your .js file as multipart/form-data under the file field. Checks that the script parses cleanly and exports a default function. Use this before uploading a script to catch syntax errors and structural problems. The return-type contract (must return a string) is enforced at indexing time by json_handler against your real JSON. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ValidateParsingScriptResponseV2 + Validation result. Returned for both valid AND invalid scripts. + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.indexing.validate_parsing_script_v2() + + + asyncio.run(main()) + """ + _response = await self._raw_client.validate_parsing_script_v2(file=file, request_options=request_options) + return _response.data diff --git a/src/runcaptain/indexing/raw_client.py b/src/runcaptain/indexing/raw_client.py index 27b0a2f..403bc13 100644 --- a/src/runcaptain/indexing/raw_client.py +++ b/src/runcaptain/indexing/raw_client.py @@ -3,16 +3,20 @@ import typing from json.decoder import JSONDecodeError +from .. import core from ..core.api_error import ApiError from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..types.index_job_response_v2 import IndexJobResponseV2 +from ..types.validate_parsing_script_response_v2 import ValidateParsingScriptResponseV2 from .types.index_azure_directory_request_v2processing_type import IndexAzureDirectoryRequestV2ProcessingType from .types.index_azure_file_request_v2processing_type import IndexAzureFileRequestV2ProcessingType from .types.index_azure_request_v2processing_type import IndexAzureRequestV2ProcessingType +from .types.index_file_v2request_processing_type import IndexFileV2RequestProcessingType from .types.index_gcs_directory_request_v2processing_type import IndexGcsDirectoryRequestV2ProcessingType from .types.index_gcs_file_request_v2processing_type import IndexGcsFileRequestV2ProcessingType from .types.index_gcs_request_v2processing_type import IndexGcsRequestV2ProcessingType @@ -26,6 +30,7 @@ from .types.index_s3file_request_v2processing_type import IndexS3FileRequestV2ProcessingType from .types.index_s3request_v2processing_type import IndexS3RequestV2ProcessingType from .types.index_url_request_v2processing_type import IndexUrlRequestV2ProcessingType +from pydantic import ValidationError # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -43,10 +48,12 @@ def index_s3bucket_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -55,6 +62,7 @@ def index_s3bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -68,6 +76,9 @@ def index_s3bucket_v2( processing_type : IndexS3RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -80,6 +91,9 @@ def index_s3bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -100,9 +114,11 @@ def index_s3bucket_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -120,6 +136,10 @@ def index_s3bucket_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_s3file_v2( @@ -133,6 +153,7 @@ def index_s3file_v2( processing_type: IndexS3FileRequestV2ProcessingType, bucket_region: typing.Optional[str] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -141,6 +162,7 @@ def index_s3file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -163,6 +185,9 @@ def index_s3file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -182,6 +207,7 @@ def index_s3file_v2( "bucket_region": bucket_region, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -202,6 +228,10 @@ def index_s3file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_gcs_bucket_v2( @@ -214,6 +244,7 @@ def index_gcs_bucket_v2( max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -222,6 +253,7 @@ def index_gcs_bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -241,6 +273,9 @@ def index_gcs_bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -259,6 +294,7 @@ def index_gcs_bucket_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -279,6 +315,10 @@ def index_gcs_bucket_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_gcs_file_v2( @@ -290,6 +330,7 @@ def index_gcs_file_v2( service_account_json: str, processing_type: IndexGcsFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -298,6 +339,7 @@ def index_gcs_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -314,6 +356,9 @@ def index_gcs_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -331,6 +376,7 @@ def index_gcs_file_v2( "service_account_json": service_account_json, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -351,6 +397,10 @@ def index_gcs_file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_s3directory_v2( @@ -362,10 +412,12 @@ def index_s3directory_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -374,6 +426,7 @@ def index_s3directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -390,6 +443,9 @@ def index_s3directory_v2( processing_type : IndexS3DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -402,6 +458,9 @@ def index_s3directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -423,9 +482,11 @@ def index_s3directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -443,6 +504,10 @@ def index_s3directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_gcs_directory_v2( @@ -453,9 +518,11 @@ def index_gcs_directory_v2( directory_path: str, service_account_json: str, processing_type: IndexGcsDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -464,6 +531,7 @@ def index_gcs_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -477,6 +545,9 @@ def index_gcs_directory_v2( processing_type : IndexGcsDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -486,6 +557,9 @@ def index_gcs_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -505,9 +579,11 @@ def index_gcs_directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -525,6 +601,10 @@ def index_gcs_directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_azure_container_v2( @@ -535,9 +615,11 @@ def index_azure_container_v2( account_name: str, account_key: str, processing_type: IndexAzureRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -546,6 +628,7 @@ def index_azure_container_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -559,6 +642,9 @@ def index_azure_container_v2( processing_type : IndexAzureRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -568,6 +654,9 @@ def index_azure_container_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -587,9 +676,11 @@ def index_azure_container_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -607,6 +698,10 @@ def index_azure_container_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_azure_file_v2( @@ -619,6 +714,7 @@ def index_azure_file_v2( account_key: str, processing_type: IndexAzureFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -627,6 +723,7 @@ def index_azure_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -646,6 +743,9 @@ def index_azure_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -664,6 +764,7 @@ def index_azure_file_v2( "account_key": account_key, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -684,6 +785,10 @@ def index_azure_file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_azure_directory_v2( @@ -695,9 +800,11 @@ def index_azure_directory_v2( account_name: str, account_key: str, processing_type: IndexAzureDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -706,6 +813,7 @@ def index_azure_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -722,6 +830,9 @@ def index_azure_directory_v2( processing_type : IndexAzureDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -731,6 +842,9 @@ def index_azure_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -751,9 +865,11 @@ def index_azure_directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -771,6 +887,10 @@ def index_azure_directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_r2bucket_v2( @@ -782,18 +902,21 @@ def index_r2bucket_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2RequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ - Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible — provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible - provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -810,6 +933,9 @@ def index_r2bucket_v2( processing_type : IndexR2RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2RequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -822,6 +948,9 @@ def index_r2bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -843,9 +972,11 @@ def index_r2bucket_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -863,6 +994,10 @@ def index_r2bucket_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_r2file_v2( @@ -877,6 +1012,7 @@ def index_r2file_v2( processing_type: IndexR2FileRequestV2ProcessingType, jurisdiction: typing.Optional[IndexR2FileRequestV2Jurisdiction] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -885,6 +1021,7 @@ def index_r2file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -910,6 +1047,9 @@ def index_r2file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -930,6 +1070,7 @@ def index_r2file_v2( "jurisdiction": jurisdiction, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -950,6 +1091,10 @@ def index_r2file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_r2directory_v2( @@ -962,10 +1107,12 @@ def index_r2directory_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ @@ -974,6 +1121,7 @@ def index_r2directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -993,6 +1141,9 @@ def index_r2directory_v2( processing_type : IndexR2DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -1005,6 +1156,9 @@ def index_r2directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1027,9 +1181,11 @@ def index_r2directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1047,6 +1203,10 @@ def index_r2directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def index_url_v2( @@ -1054,38 +1214,63 @@ def index_url_v2( collection_name: str, *, processing_type: IndexUrlRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, url: typing.Optional[str] = OMIT, urls: typing.Optional[typing.Sequence[str]] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[IndexJobResponseV2]: """ - Index documents from public URLs into a collection. No cloud storage credentials required. + Index documents or web pages from public URLs into a collection. No cloud storage credentials required. You can provide either: - - `url` — a single URL string for one document - - `urls` — an array of URL strings for multiple documents + - `url` - a single URL string + - `urls` - an array of URL strings + + ## Smart Content Detection + + The endpoint automatically detects whether a URL points to a hosted file or a web page: + + - **Hosted files** (PDF, DOCX, XLSX, CSV, TXT, images, etc.) are downloaded and processed directly through the indexing pipeline. + - **Web pages** (HTML) are automatically scraped - text content is extracted as markdown and page images are downloaded and indexed. Bot-protected pages are handled via web unlocker technology. + + ## Supported Content - Supported file types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Documents are downloaded and processed through the same pipeline as cloud storage indexing. + - **Documents**: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML + - **Images**: PNG, JPG, JPEG, GIF, BMP, TIFF + - **Web pages**: Any public URL serving HTML - text and images are extracted automatically + + ## Processing Modes for Web Pages + + - **advanced**: Extracts text content as markdown AND downloads and indexes all page images + - **basic**: Extracts text content only - faster and lower cost Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into processing_type : IndexUrlRequestV2ProcessingType - Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + Processing mode. For hosted documents: 'advanced' enables AI-enhanced extraction for complex layouts, tables, figures, and charts; 'basic' provides standard document processing. For web pages: 'advanced' extracts both text content and page images; 'basic' extracts text content only (faster, lower cost). + + idempotency_key : typing.Optional[str] + UUID for request deduplication url : typing.Optional[str] - A single public URL to a hosted document. Supported types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Provide either 'url' or 'urls', not both. + A single public URL to a document or web page. Hosted files (PDF, DOCX, etc.) are indexed directly. Web pages (HTML) are automatically scraped - text and images are extracted. Provide either 'url' or 'urls', not both. urls : typing.Optional[typing.Sequence[str]] - An array of public URLs to hosted documents. Provide either 'url' or 'urls', not both. + An array of public URLs to documents or web pages. Each URL is auto-detected - hosted files are indexed directly, web pages are scraped. Provide either 'url' or 'urls', not both. custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1102,12 +1287,280 @@ def index_url_v2( "urls": urls, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, + }, + headers={ + "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IndexJobResponseV2, + parse_obj_as( + type_=IndexJobResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def index_youtube_v2( + self, + collection_name: str, + *, + idempotency_key: typing.Optional[str] = None, + url: typing.Optional[str] = OMIT, + urls: typing.Optional[typing.Sequence[str]] = OMIT, + languages: typing.Optional[typing.Sequence[str]] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[IndexJobResponseV2]: + """ + Index YouTube video transcripts into a collection. + + Fetches transcripts from YouTube videos using auto-generated or manual captions, formats them with inline timestamps, and indexes the text for semantic search. + + You can provide either: + - `url` - a single YouTube video URL + - `urls` - an array of YouTube video URLs (max 20) + + Transcripts are always processed as basic text (no OCR needed). Each transcript is formatted with `[HH:MM:SS]` timestamp markers so search results can reference specific moments in the video. + + ## Supported URL Formats + - `youtube.com/watch?v=VIDEO_ID` + - `youtu.be/VIDEO_ID` + - `youtube.com/shorts/VIDEO_ID` + + ## Auto-Injected Metadata + The following metadata is automatically added to indexed chunks: + - `youtube_video_id` - the video ID + - `youtube_url` - the original video URL + - `youtube_language` - transcript language + - `youtube_duration_seconds` - total video duration + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + url : typing.Optional[str] + A single YouTube video URL. Supported formats: youtube.com/watch?v=, youtu.be/, youtube.com/shorts/. Provide either 'url' or 'urls', not both. + + urls : typing.Optional[typing.Sequence[str]] + An array of YouTube video URLs to index (max 20). Provide either 'url' or 'urls', not both. + + languages : typing.Optional[typing.Sequence[str]] + Preferred transcript languages in priority order (ISO 639-1 codes). Defaults to English. Only specify if you need a non-English transcript (e.g., ['fr', 'de']). Falls back to auto-generated captions if manual transcript unavailable. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[IndexJobResponseV2] + Indexing job started + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/collections/{jsonable_encoder(collection_name)}/index/youtube", + method="POST", + json={ + "url": url, + "urls": urls, + "languages": languages, + "custom_metadata": custom_metadata, + }, + headers={ + "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IndexJobResponseV2, + parse_obj_as( + type_=IndexJobResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def index_text_v2( + self, + collection_name: str, + *, + content: str, + idempotency_key: typing.Optional[str] = None, + filename: typing.Optional[str] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[IndexJobResponseV2]: + """ + Index plain text content into a collection. + + Accepts raw text content in the request body, saves it as a document, and indexes it for semantic search. No file upload or cloud storage needed. + + Text is always processed as basic (no OCR). Ideal for indexing scraped content, notes, articles, or any plain text data. + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + content : str + The text content to index. + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + filename : typing.Optional[str] + Optional filename for the text document. Defaults to 'snippet-{N}.txt' where N auto-increments. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[IndexJobResponseV2] + Indexing job started + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/collections/{jsonable_encoder(collection_name)}/index/text", + method="POST", + json={ + "content": content, + "filename": filename, + "custom_metadata": custom_metadata, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IndexJobResponseV2, + parse_obj_as( + type_=IndexJobResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def index_file_v2( + self, + collection_name: str, + *, + files: typing.List[core.File], + idempotency_key: typing.Optional[str] = None, + processing_type: typing.Optional[IndexFileV2RequestProcessingType] = OMIT, + custom_metadata: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[IndexJobResponseV2]: + """ + Upload and index files directly into a collection via multipart form-data. + + Upload one or more files (max 20) in a single request. Supports PDF, DOCX, XLSX, CSV, TXT, images, and other document types. Files are processed through the same pipeline as cloud storage indexing. + + ## Supported File Types + PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF + + ## Size Limits + - Maximum 100MB per file + - Maximum 20 files per request + + ## Processing Modes + - **advanced**: AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images (2.5 credits/page) + - **basic**: Standard document processing optimized for general indexing (1 credit/page) + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + files : typing.List[core.File] + See core.File for more documentation + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + processing_type : typing.Optional[IndexFileV2RequestProcessingType] + Document processing type: 'advanced' for AI-enhanced extraction, 'basic' for standard processing + + custom_metadata : typing.Optional[str] + JSON string of custom metadata to attach to all indexed chunks + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[IndexJobResponseV2] + Indexing job started + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/collections/{jsonable_encoder(collection_name)}/index/file", + method="POST", + data={ + "processing_type": processing_type, + "custom_metadata": custom_metadata, + }, + files={ + "files": files, + }, + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, + force_multipart=True, ) try: if 200 <= _response.status_code < 300: @@ -1122,6 +1575,59 @@ def index_url_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def validate_parsing_script_v2( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ValidateParsingScriptResponseV2]: + """ + Validates a JavaScript parsing script without running it against real data. Upload your .js file as multipart/form-data under the file field. Checks that the script parses cleanly and exports a default function. Use this before uploading a script to catch syntax errors and structural problems. The return-type contract (must return a string) is enforced at indexing time by json_handler against your real JSON. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ValidateParsingScriptResponseV2] + Validation result. Returned for both valid AND invalid scripts. + """ + _response = self._client_wrapper.httpx_client.request( + "v2/parsing-scripts/validate", + method="POST", + data={}, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ValidateParsingScriptResponseV2, + parse_obj_as( + type_=ValidateParsingScriptResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -1137,10 +1643,12 @@ async def index_s3bucket_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1149,6 +1657,7 @@ async def index_s3bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -1162,6 +1671,9 @@ async def index_s3bucket_v2( processing_type : IndexS3RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -1174,6 +1686,9 @@ async def index_s3bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1194,9 +1709,11 @@ async def index_s3bucket_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1214,6 +1731,10 @@ async def index_s3bucket_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_s3file_v2( @@ -1227,6 +1748,7 @@ async def index_s3file_v2( processing_type: IndexS3FileRequestV2ProcessingType, bucket_region: typing.Optional[str] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1235,6 +1757,7 @@ async def index_s3file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -1257,6 +1780,9 @@ async def index_s3file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1276,6 +1802,7 @@ async def index_s3file_v2( "bucket_region": bucket_region, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -1296,6 +1823,10 @@ async def index_s3file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_gcs_bucket_v2( @@ -1308,6 +1839,7 @@ async def index_gcs_bucket_v2( max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1316,6 +1848,7 @@ async def index_gcs_bucket_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -1335,6 +1868,9 @@ async def index_gcs_bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1353,6 +1889,7 @@ async def index_gcs_bucket_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -1373,6 +1910,10 @@ async def index_gcs_bucket_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_gcs_file_v2( @@ -1384,6 +1925,7 @@ async def index_gcs_file_v2( service_account_json: str, processing_type: IndexGcsFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1392,6 +1934,7 @@ async def index_gcs_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -1408,6 +1951,9 @@ async def index_gcs_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1425,6 +1971,7 @@ async def index_gcs_file_v2( "service_account_json": service_account_json, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -1445,6 +1992,10 @@ async def index_gcs_file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_s3directory_v2( @@ -1456,10 +2007,12 @@ async def index_s3directory_v2( aws_access_key_id: str, aws_secret_access_key: str, processing_type: IndexS3DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, bucket_region: typing.Optional[str] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1468,6 +2021,7 @@ async def index_s3directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the S3 bucket @@ -1484,6 +2038,9 @@ async def index_s3directory_v2( processing_type : IndexS3DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + bucket_region : typing.Optional[str] AWS region where the bucket is located @@ -1496,6 +2053,9 @@ async def index_s3directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1517,9 +2077,11 @@ async def index_s3directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1537,6 +2099,10 @@ async def index_s3directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_gcs_directory_v2( @@ -1547,9 +2113,11 @@ async def index_gcs_directory_v2( directory_path: str, service_account_json: str, processing_type: IndexGcsDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1558,6 +2126,7 @@ async def index_gcs_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the GCS bucket @@ -1571,6 +2140,9 @@ async def index_gcs_directory_v2( processing_type : IndexGcsDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -1580,6 +2152,9 @@ async def index_gcs_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1599,9 +2174,11 @@ async def index_gcs_directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1619,6 +2196,10 @@ async def index_gcs_directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_azure_container_v2( @@ -1629,9 +2210,11 @@ async def index_azure_container_v2( account_name: str, account_key: str, processing_type: IndexAzureRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1640,6 +2223,7 @@ async def index_azure_container_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -1653,6 +2237,9 @@ async def index_azure_container_v2( processing_type : IndexAzureRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -1662,6 +2249,9 @@ async def index_azure_container_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1681,9 +2271,11 @@ async def index_azure_container_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1701,6 +2293,10 @@ async def index_azure_container_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_azure_file_v2( @@ -1713,6 +2309,7 @@ async def index_azure_file_v2( account_key: str, processing_type: IndexAzureFileRequestV2ProcessingType, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1721,6 +2318,7 @@ async def index_azure_file_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -1740,6 +2338,9 @@ async def index_azure_file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1758,6 +2359,7 @@ async def index_azure_file_v2( "account_key": account_key, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -1778,6 +2380,10 @@ async def index_azure_file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_azure_directory_v2( @@ -1789,9 +2395,11 @@ async def index_azure_directory_v2( account_name: str, account_key: str, processing_type: IndexAzureDirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1800,6 +2408,7 @@ async def index_azure_directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into container_name : str Name of the Azure Blob Storage container @@ -1816,6 +2425,9 @@ async def index_azure_directory_v2( processing_type : IndexAzureDirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + max_files : typing.Optional[int] Maximum number of files to index (optional) @@ -1825,6 +2437,9 @@ async def index_azure_directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1845,9 +2460,11 @@ async def index_azure_directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1865,6 +2482,10 @@ async def index_azure_directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_r2bucket_v2( @@ -1876,18 +2497,21 @@ async def index_r2bucket_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2RequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2RequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ - Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible — provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + Index all files from a Cloudflare R2 bucket into a collection. R2 is S3-compatible - provide your R2 API token's Access Key ID and Secret Access Key. Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -1904,6 +2528,9 @@ async def index_r2bucket_v2( processing_type : IndexR2RequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2RequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -1916,6 +2543,9 @@ async def index_r2bucket_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1937,9 +2567,11 @@ async def index_r2bucket_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -1957,6 +2589,10 @@ async def index_r2bucket_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_r2file_v2( @@ -1971,6 +2607,7 @@ async def index_r2file_v2( processing_type: IndexR2FileRequestV2ProcessingType, jurisdiction: typing.Optional[IndexR2FileRequestV2Jurisdiction] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -1979,6 +2616,7 @@ async def index_r2file_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -2004,6 +2642,9 @@ async def index_r2file_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all chunks from this file. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2024,6 +2665,7 @@ async def index_r2file_v2( "jurisdiction": jurisdiction, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", @@ -2044,6 +2686,10 @@ async def index_r2file_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_r2directory_v2( @@ -2056,10 +2702,12 @@ async def index_r2directory_v2( access_key_id: str, secret_access_key: str, processing_type: IndexR2DirectoryRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, jurisdiction: typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] = OMIT, max_files: typing.Optional[int] = OMIT, skip_existing: typing.Optional[bool] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ @@ -2068,6 +2716,7 @@ async def index_r2directory_v2( Parameters ---------- collection_name : str + Name of the collection to index into bucket_name : str Name of the R2 bucket @@ -2087,6 +2736,9 @@ async def index_r2directory_v2( processing_type : IndexR2DirectoryRequestV2ProcessingType Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + idempotency_key : typing.Optional[str] + UUID for request deduplication + jurisdiction : typing.Optional[IndexR2DirectoryRequestV2Jurisdiction] R2 jurisdiction. 'default' for global, 'eu' for EU-only storage, 'fedramp' for FedRAMP-compliant storage. @@ -2099,6 +2751,9 @@ async def index_r2directory_v2( custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2121,9 +2776,11 @@ async def index_r2directory_v2( "max_files": max_files, "skip_existing": skip_existing, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -2141,6 +2798,10 @@ async def index_r2directory_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def index_url_v2( @@ -2148,38 +2809,63 @@ async def index_url_v2( collection_name: str, *, processing_type: IndexUrlRequestV2ProcessingType, + idempotency_key: typing.Optional[str] = None, url: typing.Optional[str] = OMIT, urls: typing.Optional[typing.Sequence[str]] = OMIT, custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + parsing_script: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[IndexJobResponseV2]: """ - Index documents from public URLs into a collection. No cloud storage credentials required. + Index documents or web pages from public URLs into a collection. No cloud storage credentials required. You can provide either: - - `url` — a single URL string for one document - - `urls` — an array of URL strings for multiple documents + - `url` - a single URL string + - `urls` - an array of URL strings + + ## Smart Content Detection + + The endpoint automatically detects whether a URL points to a hosted file or a web page: + + - **Hosted files** (PDF, DOCX, XLSX, CSV, TXT, images, etc.) are downloaded and processed directly through the indexing pipeline. + - **Web pages** (HTML) are automatically scraped - text content is extracted as markdown and page images are downloaded and indexed. Bot-protected pages are handled via web unlocker technology. + + ## Supported Content - Supported file types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Documents are downloaded and processed through the same pipeline as cloud storage indexing. + - **Documents**: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML + - **Images**: PNG, JPG, JPEG, GIF, BMP, TIFF + - **Web pages**: Any public URL serving HTML - text and images are extracted automatically + + ## Processing Modes for Web Pages + + - **advanced**: Extracts text content as markdown AND downloads and indexes all page images + - **basic**: Extracts text content only - faster and lower cost Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. Parameters ---------- collection_name : str + Name of the collection to index into processing_type : IndexUrlRequestV2ProcessingType - Document processing type. 'advanced' uses agentic OCR with AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images. 'basic' provides reliable OCR optimized for general document indexing and high-volume processing. + Processing mode. For hosted documents: 'advanced' enables AI-enhanced extraction for complex layouts, tables, figures, and charts; 'basic' provides standard document processing. For web pages: 'advanced' extracts both text content and page images; 'basic' extracts text content only (faster, lower cost). + + idempotency_key : typing.Optional[str] + UUID for request deduplication url : typing.Optional[str] - A single public URL to a hosted document. Supported types: PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF. Provide either 'url' or 'urls', not both. + A single public URL to a document or web page. Hosted files (PDF, DOCX, etc.) are indexed directly. Web pages (HTML) are automatically scraped - text and images are extracted. Provide either 'url' or 'urls', not both. urls : typing.Optional[typing.Sequence[str]] - An array of public URLs to hosted documents. Provide either 'url' or 'urls', not both. + An array of public URLs to documents or web pages. Each URL is auto-detected - hosted files are indexed directly, web pages are scraped. Provide either 'url' or 'urls', not both. custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + parsing_script : typing.Optional[str] + Relative path to a JavaScript parsing script for JSON files (e.g. 'research/paper-parser'). When provided, .json files are processed through a sandboxed V8 isolate that executes the script to extract text and metadata. Without this parameter, .json files are indexed as raw text. Scripts are org-scoped and managed in the Parser Studio. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2196,12 +2882,280 @@ async def index_url_v2( "urls": urls, "processing_type": processing_type, "custom_metadata": custom_metadata, + "parsing_script": parsing_script, + }, + headers={ + "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IndexJobResponseV2, + parse_obj_as( + type_=IndexJobResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def index_youtube_v2( + self, + collection_name: str, + *, + idempotency_key: typing.Optional[str] = None, + url: typing.Optional[str] = OMIT, + urls: typing.Optional[typing.Sequence[str]] = OMIT, + languages: typing.Optional[typing.Sequence[str]] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[IndexJobResponseV2]: + """ + Index YouTube video transcripts into a collection. + + Fetches transcripts from YouTube videos using auto-generated or manual captions, formats them with inline timestamps, and indexes the text for semantic search. + + You can provide either: + - `url` - a single YouTube video URL + - `urls` - an array of YouTube video URLs (max 20) + + Transcripts are always processed as basic text (no OCR needed). Each transcript is formatted with `[HH:MM:SS]` timestamp markers so search results can reference specific moments in the video. + + ## Supported URL Formats + - `youtube.com/watch?v=VIDEO_ID` + - `youtu.be/VIDEO_ID` + - `youtube.com/shorts/VIDEO_ID` + + ## Auto-Injected Metadata + The following metadata is automatically added to indexed chunks: + - `youtube_video_id` - the video ID + - `youtube_url` - the original video URL + - `youtube_language` - transcript language + - `youtube_duration_seconds` - total video duration + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + url : typing.Optional[str] + A single YouTube video URL. Supported formats: youtube.com/watch?v=, youtu.be/, youtube.com/shorts/. Provide either 'url' or 'urls', not both. + + urls : typing.Optional[typing.Sequence[str]] + An array of YouTube video URLs to index (max 20). Provide either 'url' or 'urls', not both. + + languages : typing.Optional[typing.Sequence[str]] + Preferred transcript languages in priority order (ISO 639-1 codes). Defaults to English. Only specify if you need a non-English transcript (e.g., ['fr', 'de']). Falls back to auto-generated captions if manual transcript unavailable. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[IndexJobResponseV2] + Indexing job started + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/collections/{jsonable_encoder(collection_name)}/index/youtube", + method="POST", + json={ + "url": url, + "urls": urls, + "languages": languages, + "custom_metadata": custom_metadata, + }, + headers={ + "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IndexJobResponseV2, + parse_obj_as( + type_=IndexJobResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def index_text_v2( + self, + collection_name: str, + *, + content: str, + idempotency_key: typing.Optional[str] = None, + filename: typing.Optional[str] = OMIT, + custom_metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[IndexJobResponseV2]: + """ + Index plain text content into a collection. + + Accepts raw text content in the request body, saves it as a document, and indexes it for semantic search. No file upload or cloud storage needed. + + Text is always processed as basic (no OCR). Ideal for indexing scraped content, notes, articles, or any plain text data. + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + content : str + The text content to index. + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + filename : typing.Optional[str] + Optional filename for the text document. Defaults to 'snippet-{N}.txt' where N auto-increments. + + custom_metadata : typing.Optional[typing.Dict[str, typing.Any]] + Custom metadata to attach to all indexed chunks. Keys must be strings. Values: str, int, float, bool, or array of strings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[IndexJobResponseV2] + Indexing job started + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/collections/{jsonable_encoder(collection_name)}/index/text", + method="POST", + json={ + "content": content, + "filename": filename, + "custom_metadata": custom_metadata, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IndexJobResponseV2, + parse_obj_as( + type_=IndexJobResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def index_file_v2( + self, + collection_name: str, + *, + files: typing.List[core.File], + idempotency_key: typing.Optional[str] = None, + processing_type: typing.Optional[IndexFileV2RequestProcessingType] = OMIT, + custom_metadata: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[IndexJobResponseV2]: + """ + Upload and index files directly into a collection via multipart form-data. + + Upload one or more files (max 20) in a single request. Supports PDF, DOCX, XLSX, CSV, TXT, images, and other document types. Files are processed through the same pipeline as cloud storage indexing. + + ## Supported File Types + PDF, DOCX, DOC, XLSX, XLS, CSV, TSV, TXT, MD, JSON, YAML, YML, PNG, JPG, JPEG, GIF, BMP, TIFF + + ## Size Limits + - Maximum 100MB per file + - Maximum 20 files per request + + ## Processing Modes + - **advanced**: AI-enhanced extraction for complex layouts, tables, figures, charts, and documents containing images (2.5 credits/page) + - **basic**: Standard document processing optimized for general indexing (1 credit/page) + + Returns a job_id for tracking progress via GET /v2/jobs/{job_id}. + + Parameters + ---------- + collection_name : str + Name of the collection to index into + + files : typing.List[core.File] + See core.File for more documentation + + idempotency_key : typing.Optional[str] + UUID for request deduplication + + processing_type : typing.Optional[IndexFileV2RequestProcessingType] + Document processing type: 'advanced' for AI-enhanced extraction, 'basic' for standard processing + + custom_metadata : typing.Optional[str] + JSON string of custom metadata to attach to all indexed chunks + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[IndexJobResponseV2] + Indexing job started + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/collections/{jsonable_encoder(collection_name)}/index/file", + method="POST", + data={ + "processing_type": processing_type, + "custom_metadata": custom_metadata, + }, + files={ + "files": files, + }, + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, + force_multipart=True, ) try: if 200 <= _response.status_code < 300: @@ -2216,4 +3170,57 @@ async def index_url_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def validate_parsing_script_v2( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ValidateParsingScriptResponseV2]: + """ + Validates a JavaScript parsing script without running it against real data. Upload your .js file as multipart/form-data under the file field. Checks that the script parses cleanly and exports a default function. Use this before uploading a script to catch syntax errors and structural problems. The return-type contract (must return a string) is enforced at indexing time by json_handler against your real JSON. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ValidateParsingScriptResponseV2] + Validation result. Returned for both valid AND invalid scripts. + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/parsing-scripts/validate", + method="POST", + data={}, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ValidateParsingScriptResponseV2, + parse_obj_as( + type_=ValidateParsingScriptResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/indexing/types/__init__.py b/src/runcaptain/indexing/types/__init__.py index 817f73a..c2ce2b0 100644 --- a/src/runcaptain/indexing/types/__init__.py +++ b/src/runcaptain/indexing/types/__init__.py @@ -9,6 +9,7 @@ from .index_azure_directory_request_v2processing_type import IndexAzureDirectoryRequestV2ProcessingType from .index_azure_file_request_v2processing_type import IndexAzureFileRequestV2ProcessingType from .index_azure_request_v2processing_type import IndexAzureRequestV2ProcessingType + from .index_file_v2request_processing_type import IndexFileV2RequestProcessingType from .index_gcs_directory_request_v2processing_type import IndexGcsDirectoryRequestV2ProcessingType from .index_gcs_file_request_v2processing_type import IndexGcsFileRequestV2ProcessingType from .index_gcs_request_v2processing_type import IndexGcsRequestV2ProcessingType @@ -26,6 +27,7 @@ "IndexAzureDirectoryRequestV2ProcessingType": ".index_azure_directory_request_v2processing_type", "IndexAzureFileRequestV2ProcessingType": ".index_azure_file_request_v2processing_type", "IndexAzureRequestV2ProcessingType": ".index_azure_request_v2processing_type", + "IndexFileV2RequestProcessingType": ".index_file_v2request_processing_type", "IndexGcsDirectoryRequestV2ProcessingType": ".index_gcs_directory_request_v2processing_type", "IndexGcsFileRequestV2ProcessingType": ".index_gcs_file_request_v2processing_type", "IndexGcsRequestV2ProcessingType": ".index_gcs_request_v2processing_type", @@ -67,6 +69,7 @@ def __dir__(): "IndexAzureDirectoryRequestV2ProcessingType", "IndexAzureFileRequestV2ProcessingType", "IndexAzureRequestV2ProcessingType", + "IndexFileV2RequestProcessingType", "IndexGcsDirectoryRequestV2ProcessingType", "IndexGcsFileRequestV2ProcessingType", "IndexGcsRequestV2ProcessingType", diff --git a/src/runcaptain/indexing/types/index_file_v2request_processing_type.py b/src/runcaptain/indexing/types/index_file_v2request_processing_type.py new file mode 100644 index 0000000..4eb77d2 --- /dev/null +++ b/src/runcaptain/indexing/types/index_file_v2request_processing_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +IndexFileV2RequestProcessingType = typing.Union[typing.Literal["advanced", "basic"], typing.Any] diff --git a/src/runcaptain/investors/__init__.py b/src/runcaptain/investors/__init__.py index 5cde020..98fc3fe 100644 --- a/src/runcaptain/investors/__init__.py +++ b/src/runcaptain/investors/__init__.py @@ -2,3 +2,87 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + InvestorsActiveInvestmentsResponse, + InvestorsActiveInvestmentsResponsePortfolioItem, + InvestorsBioResponse, + InvestorsBioResponseHeadquarters, + InvestorsBioResponseInvestorType, + InvestorsBoardSeatsResponse, + InvestorsBoardSeatsResponseBoardSeatsItem, + InvestorsFundsLatestResponse, + InvestorsFundsLatestResponseFundsItem, + InvestorsFundsLatestResponseLatestFund, + InvestorsFundsResponse, + InvestorsFundsResponseFundsItem, + InvestorsPreferencesResponse, + InvestorsSearchResponse, + InvestorsSearchResponseResultsItem, + InvestorsServiceProvidersDealResponse, + InvestorsServiceProvidersResponse, + ) +_dynamic_imports: typing.Dict[str, str] = { + "InvestorsActiveInvestmentsResponse": ".types", + "InvestorsActiveInvestmentsResponsePortfolioItem": ".types", + "InvestorsBioResponse": ".types", + "InvestorsBioResponseHeadquarters": ".types", + "InvestorsBioResponseInvestorType": ".types", + "InvestorsBoardSeatsResponse": ".types", + "InvestorsBoardSeatsResponseBoardSeatsItem": ".types", + "InvestorsFundsLatestResponse": ".types", + "InvestorsFundsLatestResponseFundsItem": ".types", + "InvestorsFundsLatestResponseLatestFund": ".types", + "InvestorsFundsResponse": ".types", + "InvestorsFundsResponseFundsItem": ".types", + "InvestorsPreferencesResponse": ".types", + "InvestorsSearchResponse": ".types", + "InvestorsSearchResponseResultsItem": ".types", + "InvestorsServiceProvidersDealResponse": ".types", + "InvestorsServiceProvidersResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "InvestorsActiveInvestmentsResponse", + "InvestorsActiveInvestmentsResponsePortfolioItem", + "InvestorsBioResponse", + "InvestorsBioResponseHeadquarters", + "InvestorsBioResponseInvestorType", + "InvestorsBoardSeatsResponse", + "InvestorsBoardSeatsResponseBoardSeatsItem", + "InvestorsFundsLatestResponse", + "InvestorsFundsLatestResponseFundsItem", + "InvestorsFundsLatestResponseLatestFund", + "InvestorsFundsResponse", + "InvestorsFundsResponseFundsItem", + "InvestorsPreferencesResponse", + "InvestorsSearchResponse", + "InvestorsSearchResponseResultsItem", + "InvestorsServiceProvidersDealResponse", + "InvestorsServiceProvidersResponse", +] diff --git a/src/runcaptain/investors/client.py b/src/runcaptain/investors/client.py index 571f1ea..861929a 100644 --- a/src/runcaptain/investors/client.py +++ b/src/runcaptain/investors/client.py @@ -5,6 +5,15 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawInvestorsClient, RawInvestorsClient +from .types.investors_active_investments_response import InvestorsActiveInvestmentsResponse +from .types.investors_bio_response import InvestorsBioResponse +from .types.investors_board_seats_response import InvestorsBoardSeatsResponse +from .types.investors_funds_latest_response import InvestorsFundsLatestResponse +from .types.investors_funds_response import InvestorsFundsResponse +from .types.investors_preferences_response import InvestorsPreferencesResponse +from .types.investors_search_response import InvestorsSearchResponse +from .types.investors_service_providers_deal_response import InvestorsServiceProvidersDealResponse +from .types.investors_service_providers_response import InvestorsServiceProvidersResponse class InvestorsClient: @@ -25,15 +34,23 @@ def with_raw_response(self) -> RawInvestorsClient: def search( self, *, + q: typing.Optional[str] = None, + investor_type: typing.Optional[str] = None, page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsSearchResponse: """ Search for venture capital firms, angel investors, and institutional investors by name. Returns matching investor profiles with investment focus, portfolio size, and notable investments. Use this to find investor entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Investor name or keyword (e.g., 'Sequoia Capital') + + investor_type : typing.Optional[str] + Filter by investor type + page : typing.Optional[int] Page number @@ -45,7 +62,7 @@ def search( Returns ------- - typing.Dict[str, typing.Any] + InvestorsSearchResponse Successful response Examples @@ -58,23 +75,26 @@ def search( ) client.investors.search() """ - _response = self._raw_client.search(page=page, page_size=page_size, request_options=request_options) + _response = self._raw_client.search( + q=q, investor_type=investor_type, page=page, page_size=page_size, request_options=request_options + ) return _response.data - def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> InvestorsBioResponse: """ Get comprehensive investor profile including description, investment thesis, stage focus, sector focus, and notable portfolio companies. This is the primary endpoint for investor overview data. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsBioResponse Successful response Examples @@ -93,54 +113,35 @@ def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = Non return _response.data def active_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> InvestorsActiveInvestmentsResponse: """ Get current portfolio companies that the investor has active positions in. Returns company names, investment dates, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.investors.active_investments( - id="deal_openai_0", - ) - """ - _response = self._raw_client.active_investments(id, request_options=request_options) - return _response.data - - def all_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete investment history including current portfolio and exited positions. Returns all companies the investor has backed with investment details and outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsActiveInvestmentsResponse Successful response Examples @@ -151,29 +152,32 @@ def all_investments( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.investors.all_investments( + client.investors.active_investments( id="deal_openai_0", ) """ - _response = self._raw_client.all_investments(id, request_options=request_options) + _response = self._raw_client.active_investments( + id, page=page, page_size=page_size, request_options=request_options + ) return _response.data def preferences( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsPreferencesResponse: """ Get investment preferences including stage focus, sector preferences, geography, and typical check size. Useful for determining investment fit. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsPreferencesResponse Successful response Examples @@ -191,22 +195,21 @@ def preferences( _response = self._raw_client.preferences(id, request_options=request_options) return _response.data - def funds( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def funds(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> InvestorsFundsResponse: """ Get all funds managed by the investor including fund names, sizes, vintage years, and status. Returns complete fund portfolio for multi-fund investors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsFundsResponse Successful response Examples @@ -226,20 +229,21 @@ def funds( def funds_latest( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsFundsLatestResponse: """ Get information about the investor's most recent fund including size, vintage year, and deployment status. Useful for understanding current investment capacity. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsFundsLatestResponse Successful response Examples @@ -259,20 +263,21 @@ def funds_latest( def board_seats( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsBoardSeatsResponse: """ Get board seats held by the investor's team members. Returns companies where investor partners serve on the board of directors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsBoardSeatsResponse Successful response Examples @@ -292,20 +297,21 @@ def board_seats( def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsServiceProvidersResponse: """ Get service providers used by the investor including legal counsel, fund administrators, and consultants. Returns firm names and service types. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsServiceProvidersResponse Successful response Examples @@ -325,20 +331,21 @@ def service_providers( def service_providers_deal( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsServiceProvidersDealResponse: """ Get service providers involved in the investor's deal flow including transaction advisors and due diligence firms. Returns provider details specific to deal execution. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsServiceProvidersDealResponse Successful response Examples @@ -356,39 +363,6 @@ def service_providers_deal( _response = self._raw_client.service_providers_deal(id, request_options=request_options) return _response.data - def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to investor profile data. Returns history of changes including new investments, fund raises, and team changes with timestamps. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.investors.updates( - id="deal_openai_0", - ) - """ - _response = self._raw_client.updates(id, request_options=request_options) - return _response.data - class AsyncInvestorsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -408,15 +382,23 @@ def with_raw_response(self) -> AsyncRawInvestorsClient: async def search( self, *, + q: typing.Optional[str] = None, + investor_type: typing.Optional[str] = None, page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsSearchResponse: """ Search for venture capital firms, angel investors, and institutional investors by name. Returns matching investor profiles with investment focus, portfolio size, and notable investments. Use this to find investor entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Investor name or keyword (e.g., 'Sequoia Capital') + + investor_type : typing.Optional[str] + Filter by investor type + page : typing.Optional[int] Page number @@ -428,7 +410,7 @@ async def search( Returns ------- - typing.Dict[str, typing.Any] + InvestorsSearchResponse Successful response Examples @@ -449,25 +431,26 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.search(page=page, page_size=page_size, request_options=request_options) + _response = await self._raw_client.search( + q=q, investor_type=investor_type, page=page, page_size=page_size, request_options=request_options + ) return _response.data - async def bio( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def bio(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> InvestorsBioResponse: """ Get comprehensive investor profile including description, investment thesis, stage focus, sector focus, and notable portfolio companies. This is the primary endpoint for investor overview data. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsBioResponse Successful response Examples @@ -494,62 +477,35 @@ async def main() -> None: return _response.data async def active_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> InvestorsActiveInvestmentsResponse: """ Get current portfolio companies that the investor has active positions in. Returns company names, investment dates, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.investors.active_investments( - id="deal_openai_0", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.active_investments(id, request_options=request_options) - return _response.data - - async def all_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete investment history including current portfolio and exited positions. Returns all companies the investor has backed with investment details and outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsActiveInvestmentsResponse Successful response Examples @@ -565,32 +521,35 @@ async def all_investments( async def main() -> None: - await client.investors.all_investments( + await client.investors.active_investments( id="deal_openai_0", ) asyncio.run(main()) """ - _response = await self._raw_client.all_investments(id, request_options=request_options) + _response = await self._raw_client.active_investments( + id, page=page, page_size=page_size, request_options=request_options + ) return _response.data async def preferences( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsPreferencesResponse: """ Get investment preferences including stage focus, sector preferences, geography, and typical check size. Useful for determining investment fit. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsPreferencesResponse Successful response Examples @@ -618,20 +577,21 @@ async def main() -> None: async def funds( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsFundsResponse: """ Get all funds managed by the investor including fund names, sizes, vintage years, and status. Returns complete fund portfolio for multi-fund investors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsFundsResponse Successful response Examples @@ -659,20 +619,21 @@ async def main() -> None: async def funds_latest( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsFundsLatestResponse: """ Get information about the investor's most recent fund including size, vintage year, and deployment status. Useful for understanding current investment capacity. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsFundsLatestResponse Successful response Examples @@ -700,20 +661,21 @@ async def main() -> None: async def board_seats( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsBoardSeatsResponse: """ Get board seats held by the investor's team members. Returns companies where investor partners serve on the board of directors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsBoardSeatsResponse Successful response Examples @@ -741,20 +703,21 @@ async def main() -> None: async def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsServiceProvidersResponse: """ Get service providers used by the investor including legal counsel, fund administrators, and consultants. Returns firm names and service types. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsServiceProvidersResponse Successful response Examples @@ -782,20 +745,21 @@ async def main() -> None: async def service_providers_deal( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> InvestorsServiceProvidersDealResponse: """ Get service providers involved in the investor's deal flow including transaction advisors and due diligence firms. Returns provider details specific to deal execution. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + InvestorsServiceProvidersDealResponse Successful response Examples @@ -820,44 +784,3 @@ async def main() -> None: """ _response = await self._raw_client.service_providers_deal(id, request_options=request_options) return _response.data - - async def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to investor profile data. Returns history of changes including new investments, fund raises, and team changes with timestamps. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.investors.updates( - id="deal_openai_0", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.updates(id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/investors/raw_client.py b/src/runcaptain/investors/raw_client.py index e94956a..5866e9c 100644 --- a/src/runcaptain/investors/raw_client.py +++ b/src/runcaptain/investors/raw_client.py @@ -7,11 +7,22 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.investors_active_investments_response import InvestorsActiveInvestmentsResponse +from .types.investors_bio_response import InvestorsBioResponse +from .types.investors_board_seats_response import InvestorsBoardSeatsResponse +from .types.investors_funds_latest_response import InvestorsFundsLatestResponse +from .types.investors_funds_response import InvestorsFundsResponse +from .types.investors_preferences_response import InvestorsPreferencesResponse +from .types.investors_search_response import InvestorsSearchResponse +from .types.investors_service_providers_deal_response import InvestorsServiceProvidersDealResponse +from .types.investors_service_providers_response import InvestorsServiceProvidersResponse +from pydantic import ValidationError class RawInvestorsClient: @@ -21,15 +32,23 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def search( self, *, + q: typing.Optional[str] = None, + investor_type: typing.Optional[str] = None, page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsSearchResponse]: """ Search for venture capital firms, angel investors, and institutional investors by name. Returns matching investor profiles with investment focus, portfolio size, and notable investments. Use this to find investor entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Investor name or keyword (e.g., 'Sequoia Capital') + + investor_type : typing.Optional[str] + Filter by investor type + page : typing.Optional[int] Page number @@ -41,13 +60,15 @@ def search( Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/investors/search", method="GET", params={ + "q": q, + "investor_type": investor_type, "page": page, "page_size": page_size, }, @@ -56,9 +77,9 @@ def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -77,24 +98,29 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def bio( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsBioResponse]: """ Get comprehensive investor profile including description, investment thesis, stage focus, sector focus, and notable portfolio companies. This is the primary endpoint for investor overview data. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsBioResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -105,9 +131,9 @@ def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -137,97 +163,59 @@ def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def active_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[InvestorsActiveInvestmentsResponse]: """ Get current portfolio companies that the investor has active positions in. Returns company names, investment dates, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/active-investments", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def all_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete investment history including current portfolio and exited positions. Returns all companies the investor has backed with investment details and outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsActiveInvestmentsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/all-investments", + f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/active-investments", method="GET", + params={ + "page": page, + "page_size": page_size, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsActiveInvestmentsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsActiveInvestmentsResponse, # type: ignore object_=_response.json(), ), ) @@ -257,24 +245,29 @@ def all_investments( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def preferences( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsPreferencesResponse]: """ Get investment preferences including stage focus, sector preferences, geography, and typical check size. Useful for determining investment fit. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsPreferencesResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -285,9 +278,9 @@ def preferences( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsPreferencesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsPreferencesResponse, # type: ignore object_=_response.json(), ), ) @@ -317,24 +310,29 @@ def preferences( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def funds( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsFundsResponse]: """ Get all funds managed by the investor including fund names, sizes, vintage years, and status. Returns complete fund portfolio for multi-fund investors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsFundsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -345,9 +343,9 @@ def funds( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsFundsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsFundsResponse, # type: ignore object_=_response.json(), ), ) @@ -388,24 +386,29 @@ def funds( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def funds_latest( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsFundsLatestResponse]: """ Get information about the investor's most recent fund including size, vintage year, and deployment status. Useful for understanding current investment capacity. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsFundsLatestResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -416,9 +419,9 @@ def funds_latest( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsFundsLatestResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsFundsLatestResponse, # type: ignore object_=_response.json(), ), ) @@ -448,24 +451,29 @@ def funds_latest( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def board_seats( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsBoardSeatsResponse]: """ Get board seats held by the investor's team members. Returns companies where investor partners serve on the board of directors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsBoardSeatsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -476,9 +484,9 @@ def board_seats( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsBoardSeatsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsBoardSeatsResponse, # type: ignore object_=_response.json(), ), ) @@ -508,24 +516,29 @@ def board_seats( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsServiceProvidersResponse]: """ Get service providers used by the investor including legal counsel, fund administrators, and consultants. Returns firm names and service types. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsServiceProvidersResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -536,9 +549,9 @@ def service_providers( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsServiceProvidersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsServiceProvidersResponse, # type: ignore object_=_response.json(), ), ) @@ -579,24 +592,29 @@ def service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def service_providers_deal( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[InvestorsServiceProvidersDealResponse]: """ Get service providers involved in the investor's deal flow including transaction advisors and due diligence firms. Returns provider details specific to deal execution. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[InvestorsServiceProvidersDealResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -607,9 +625,9 @@ def service_providers_deal( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsServiceProvidersDealResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsServiceProvidersDealResponse, # type: ignore object_=_response.json(), ), ) @@ -650,66 +668,10 @@ def service_providers_deal( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to investor profile data. Returns history of changes including new investments, fund raises, and team changes with timestamps. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -720,15 +682,23 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def search( self, *, + q: typing.Optional[str] = None, + investor_type: typing.Optional[str] = None, page: typing.Optional[int] = None, page_size: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsSearchResponse]: """ Search for venture capital firms, angel investors, and institutional investors by name. Returns matching investor profiles with investment focus, portfolio size, and notable investments. Use this to find investor entity IDs for detailed lookups. Parameters ---------- + q : typing.Optional[str] + Investor name or keyword (e.g., 'Sequoia Capital') + + investor_type : typing.Optional[str] + Filter by investor type + page : typing.Optional[int] Page number @@ -740,13 +710,15 @@ async def search( Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/investors/search", method="GET", params={ + "q": q, + "investor_type": investor_type, "page": page, "page_size": page_size, }, @@ -755,9 +727,9 @@ async def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -776,24 +748,29 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def bio( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsBioResponse]: """ Get comprehensive investor profile including description, investment thesis, stage focus, sector focus, and notable portfolio companies. This is the primary endpoint for investor overview data. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsBioResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -804,9 +781,9 @@ async def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -836,97 +813,59 @@ async def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def active_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + id: str, + *, + page: typing.Optional[int] = None, + page_size: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[InvestorsActiveInvestmentsResponse]: """ Get current portfolio companies that the investor has active positions in. Returns company names, investment dates, and current status. + Supports pagination via `page` and `page_size` query parameters. Response includes `total_in_database` for the full count across all pages. + Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/active-investments", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def all_investments( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete investment history including current portfolio and exited positions. Returns all companies the investor has backed with investment details and outcomes. + page : typing.Optional[int] + Page number for pagination (default: 1) - Parameters - ---------- - id : str + page_size : typing.Optional[int] + Results per page (default: 50, max: 1000) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsActiveInvestmentsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/all-investments", + f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/active-investments", method="GET", + params={ + "page": page, + "page_size": page_size, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsActiveInvestmentsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsActiveInvestmentsResponse, # type: ignore object_=_response.json(), ), ) @@ -956,24 +895,29 @@ async def all_investments( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def preferences( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsPreferencesResponse]: """ Get investment preferences including stage focus, sector preferences, geography, and typical check size. Useful for determining investment fit. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsPreferencesResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -984,9 +928,9 @@ async def preferences( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsPreferencesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsPreferencesResponse, # type: ignore object_=_response.json(), ), ) @@ -1016,24 +960,29 @@ async def preferences( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def funds( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsFundsResponse]: """ Get all funds managed by the investor including fund names, sizes, vintage years, and status. Returns complete fund portfolio for multi-fund investors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsFundsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1044,9 +993,9 @@ async def funds( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsFundsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsFundsResponse, # type: ignore object_=_response.json(), ), ) @@ -1087,24 +1036,29 @@ async def funds( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def funds_latest( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsFundsLatestResponse]: """ Get information about the investor's most recent fund including size, vintage year, and deployment status. Useful for understanding current investment capacity. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsFundsLatestResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1115,9 +1069,9 @@ async def funds_latest( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsFundsLatestResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsFundsLatestResponse, # type: ignore object_=_response.json(), ), ) @@ -1147,24 +1101,29 @@ async def funds_latest( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def board_seats( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsBoardSeatsResponse]: """ Get board seats held by the investor's team members. Returns companies where investor partners serve on the board of directors. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsBoardSeatsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1175,9 +1134,9 @@ async def board_seats( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsBoardSeatsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsBoardSeatsResponse, # type: ignore object_=_response.json(), ), ) @@ -1207,24 +1166,29 @@ async def board_seats( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def service_providers( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsServiceProvidersResponse]: """ Get service providers used by the investor including legal counsel, fund administrators, and consultants. Returns firm names and service types. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsServiceProvidersResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1235,9 +1199,9 @@ async def service_providers( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsServiceProvidersResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsServiceProvidersResponse, # type: ignore object_=_response.json(), ), ) @@ -1278,24 +1242,29 @@ async def service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def service_providers_deal( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[InvestorsServiceProvidersDealResponse]: """ Get service providers involved in the investor's deal flow including transaction advisors and due diligence firms. Returns provider details specific to deal execution. Parameters ---------- id : str + Investor name (e.g., 'Sequoia Capital') or entity ID from /investors/search request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[InvestorsServiceProvidersDealResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -1306,9 +1275,9 @@ async def service_providers_deal( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + InvestorsServiceProvidersDealResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=InvestorsServiceProvidersDealResponse, # type: ignore object_=_response.json(), ), ) @@ -1349,64 +1318,8 @@ async def service_providers_deal( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def updates( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to investor profile data. Returns history of changes including new investments, fund raises, and team changes with timestamps. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/investors/{jsonable_encoder(id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/investors/types/__init__.py b/src/runcaptain/investors/types/__init__.py new file mode 100644 index 0000000..9191a5c --- /dev/null +++ b/src/runcaptain/investors/types/__init__.py @@ -0,0 +1,86 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .investors_active_investments_response import InvestorsActiveInvestmentsResponse + from .investors_active_investments_response_portfolio_item import InvestorsActiveInvestmentsResponsePortfolioItem + from .investors_bio_response import InvestorsBioResponse + from .investors_bio_response_headquarters import InvestorsBioResponseHeadquarters + from .investors_bio_response_investor_type import InvestorsBioResponseInvestorType + from .investors_board_seats_response import InvestorsBoardSeatsResponse + from .investors_board_seats_response_board_seats_item import InvestorsBoardSeatsResponseBoardSeatsItem + from .investors_funds_latest_response import InvestorsFundsLatestResponse + from .investors_funds_latest_response_funds_item import InvestorsFundsLatestResponseFundsItem + from .investors_funds_latest_response_latest_fund import InvestorsFundsLatestResponseLatestFund + from .investors_funds_response import InvestorsFundsResponse + from .investors_funds_response_funds_item import InvestorsFundsResponseFundsItem + from .investors_preferences_response import InvestorsPreferencesResponse + from .investors_search_response import InvestorsSearchResponse + from .investors_search_response_results_item import InvestorsSearchResponseResultsItem + from .investors_service_providers_deal_response import InvestorsServiceProvidersDealResponse + from .investors_service_providers_response import InvestorsServiceProvidersResponse +_dynamic_imports: typing.Dict[str, str] = { + "InvestorsActiveInvestmentsResponse": ".investors_active_investments_response", + "InvestorsActiveInvestmentsResponsePortfolioItem": ".investors_active_investments_response_portfolio_item", + "InvestorsBioResponse": ".investors_bio_response", + "InvestorsBioResponseHeadquarters": ".investors_bio_response_headquarters", + "InvestorsBioResponseInvestorType": ".investors_bio_response_investor_type", + "InvestorsBoardSeatsResponse": ".investors_board_seats_response", + "InvestorsBoardSeatsResponseBoardSeatsItem": ".investors_board_seats_response_board_seats_item", + "InvestorsFundsLatestResponse": ".investors_funds_latest_response", + "InvestorsFundsLatestResponseFundsItem": ".investors_funds_latest_response_funds_item", + "InvestorsFundsLatestResponseLatestFund": ".investors_funds_latest_response_latest_fund", + "InvestorsFundsResponse": ".investors_funds_response", + "InvestorsFundsResponseFundsItem": ".investors_funds_response_funds_item", + "InvestorsPreferencesResponse": ".investors_preferences_response", + "InvestorsSearchResponse": ".investors_search_response", + "InvestorsSearchResponseResultsItem": ".investors_search_response_results_item", + "InvestorsServiceProvidersDealResponse": ".investors_service_providers_deal_response", + "InvestorsServiceProvidersResponse": ".investors_service_providers_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "InvestorsActiveInvestmentsResponse", + "InvestorsActiveInvestmentsResponsePortfolioItem", + "InvestorsBioResponse", + "InvestorsBioResponseHeadquarters", + "InvestorsBioResponseInvestorType", + "InvestorsBoardSeatsResponse", + "InvestorsBoardSeatsResponseBoardSeatsItem", + "InvestorsFundsLatestResponse", + "InvestorsFundsLatestResponseFundsItem", + "InvestorsFundsLatestResponseLatestFund", + "InvestorsFundsResponse", + "InvestorsFundsResponseFundsItem", + "InvestorsPreferencesResponse", + "InvestorsSearchResponse", + "InvestorsSearchResponseResultsItem", + "InvestorsServiceProvidersDealResponse", + "InvestorsServiceProvidersResponse", +] diff --git a/src/runcaptain/investors/types/investors_active_investments_response.py b/src/runcaptain/investors/types/investors_active_investments_response.py new file mode 100644 index 0000000..97f54d4 --- /dev/null +++ b/src/runcaptain/investors/types/investors_active_investments_response.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .investors_active_investments_response_portfolio_item import InvestorsActiveInvestmentsResponsePortfolioItem + + +class InvestorsActiveInvestmentsResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + portfolio: typing.Optional[typing.List[InvestorsActiveInvestmentsResponsePortfolioItem]] = None + total_companies: typing.Optional[int] = pydantic.Field(default=None) + """ + Total portfolio companies + """ + + active_companies: typing.Optional[int] = pydantic.Field(default=None) + """ + Active portfolio companies + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_active_investments_response_portfolio_item.py b/src/runcaptain/investors/types/investors_active_investments_response_portfolio_item.py new file mode 100644 index 0000000..c94a9a2 --- /dev/null +++ b/src/runcaptain/investors/types/investors_active_investments_response_portfolio_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsActiveInvestmentsResponsePortfolioItem(UniversalBaseModel): + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + company_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + investment_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Investment date + """ + + deal_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal type + """ + + amount_invested: typing.Optional[float] = pydantic.Field(default=None) + """ + Amount invested in USD + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Investment status + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_bio_response.py b/src/runcaptain/investors/types/investors_bio_response.py new file mode 100644 index 0000000..f7f21b9 --- /dev/null +++ b/src/runcaptain/investors/types/investors_bio_response.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .investors_bio_response_headquarters import InvestorsBioResponseHeadquarters +from .investors_bio_response_investor_type import InvestorsBioResponseInvestorType + + +class InvestorsBioResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Always 'investor' + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name + """ + + investor_type: typing.Optional[InvestorsBioResponseInvestorType] = None + founded: typing.Optional[int] = pydantic.Field(default=None) + """ + Year founded + """ + + headquarters: typing.Optional[InvestorsBioResponseHeadquarters] = None + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + data_sources: typing.Optional[typing.List[str]] = None + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor description + """ + + total_investments: typing.Optional[int] = pydantic.Field(default=None) + """ + Total portfolio investments + """ + + active_portfolio_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Active portfolio companies + """ + + total_funds: typing.Optional[int] = pydantic.Field(default=None) + """ + Total managed funds + """ + + assets_under_management: typing.Optional[float] = pydantic.Field(default=None) + """ + AUM in USD + """ + + notable_exits: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Notable exit companies + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn URL + """ + + crunchbase_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Crunchbase URL + """ + + cached_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Cache timestamp (ISO 8601) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_bio_response_headquarters.py b/src/runcaptain/investors/types/investors_bio_response_headquarters.py new file mode 100644 index 0000000..7fc80cf --- /dev/null +++ b/src/runcaptain/investors/types/investors_bio_response_headquarters.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsBioResponseHeadquarters(UniversalBaseModel): + city: typing.Optional[str] = None + state: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_bio_response_investor_type.py b/src/runcaptain/investors/types/investors_bio_response_investor_type.py new file mode 100644 index 0000000..d309b47 --- /dev/null +++ b/src/runcaptain/investors/types/investors_bio_response_investor_type.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsBioResponseInvestorType(UniversalBaseModel): + type: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor type (vc, pe, angel, corporate) + """ + + stage_focus: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Stage focus areas + """ + + sector_focus: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Sector focus areas + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_board_seats_response.py b/src/runcaptain/investors/types/investors_board_seats_response.py new file mode 100644 index 0000000..190c927 --- /dev/null +++ b/src/runcaptain/investors/types/investors_board_seats_response.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .investors_board_seats_response_board_seats_item import InvestorsBoardSeatsResponseBoardSeatsItem + + +class InvestorsBoardSeatsResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + board_seats: typing.Optional[typing.List[InvestorsBoardSeatsResponseBoardSeatsItem]] = None + total_seats: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of board seats + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_board_seats_response_board_seats_item.py b/src/runcaptain/investors/types/investors_board_seats_response_board_seats_item.py new file mode 100644 index 0000000..7ef24f4 --- /dev/null +++ b/src/runcaptain/investors/types/investors_board_seats_response_board_seats_item.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsBoardSeatsResponseBoardSeatsItem(UniversalBaseModel): + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + company_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + board_member: typing.Optional[str] = pydantic.Field(default=None) + """ + Board member name + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Board role + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_funds_latest_response.py b/src/runcaptain/investors/types/investors_funds_latest_response.py new file mode 100644 index 0000000..2a0b01f --- /dev/null +++ b/src/runcaptain/investors/types/investors_funds_latest_response.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .investors_funds_latest_response_funds_item import InvestorsFundsLatestResponseFundsItem +from .investors_funds_latest_response_latest_fund import InvestorsFundsLatestResponseLatestFund + + +class InvestorsFundsLatestResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + funds: typing.Optional[typing.List[InvestorsFundsLatestResponseFundsItem]] = None + total_funds: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of funds + """ + + total_aum: typing.Optional[float] = pydantic.Field(default=None) + """ + Total assets under management + """ + + latest_fund: typing.Optional[InvestorsFundsLatestResponseLatestFund] = pydantic.Field(default=None) + """ + Most recent fund details + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_funds_latest_response_funds_item.py b/src/runcaptain/investors/types/investors_funds_latest_response_funds_item.py new file mode 100644 index 0000000..e8531a9 --- /dev/null +++ b/src/runcaptain/investors/types/investors_funds_latest_response_funds_item.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsFundsLatestResponseFundsItem(UniversalBaseModel): + fund_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund name + """ + + fund_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund entity ID + """ + + fund_size: typing.Optional[float] = pydantic.Field(default=None) + """ + Fund size in USD + """ + + vintage_year: typing.Optional[int] = pydantic.Field(default=None) + """ + Vintage year + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund status + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_funds_latest_response_latest_fund.py b/src/runcaptain/investors/types/investors_funds_latest_response_latest_fund.py new file mode 100644 index 0000000..78a2994 --- /dev/null +++ b/src/runcaptain/investors/types/investors_funds_latest_response_latest_fund.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsFundsLatestResponseLatestFund(UniversalBaseModel): + """ + Most recent fund details + """ + + fund_name: typing.Optional[str] = None + fund_id: typing.Optional[str] = None + fund_size: typing.Optional[float] = None + vintage_year: typing.Optional[int] = None + status: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_funds_response.py b/src/runcaptain/investors/types/investors_funds_response.py new file mode 100644 index 0000000..f1bd6bc --- /dev/null +++ b/src/runcaptain/investors/types/investors_funds_response.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .investors_funds_response_funds_item import InvestorsFundsResponseFundsItem + + +class InvestorsFundsResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + funds: typing.Optional[typing.List[InvestorsFundsResponseFundsItem]] = None + total_funds: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of funds + """ + + total_aum: typing.Optional[float] = pydantic.Field(default=None) + """ + Total assets under management + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_funds_response_funds_item.py b/src/runcaptain/investors/types/investors_funds_response_funds_item.py new file mode 100644 index 0000000..68795d6 --- /dev/null +++ b/src/runcaptain/investors/types/investors_funds_response_funds_item.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsFundsResponseFundsItem(UniversalBaseModel): + fund_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund name + """ + + fund_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund entity ID + """ + + fund_size: typing.Optional[float] = pydantic.Field(default=None) + """ + Fund size in USD + """ + + vintage_year: typing.Optional[int] = pydantic.Field(default=None) + """ + Vintage year + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund status + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_preferences_response.py b/src/runcaptain/investors/types/investors_preferences_response.py new file mode 100644 index 0000000..6bb6358 --- /dev/null +++ b/src/runcaptain/investors/types/investors_preferences_response.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsPreferencesResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + stages: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Preferred investment stages + """ + + sectors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Preferred sectors + """ + + geographies: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Preferred geographies + """ + + check_size_min: typing.Optional[float] = pydantic.Field(default=None) + """ + Minimum check size in USD + """ + + check_size_max: typing.Optional[float] = pydantic.Field(default=None) + """ + Maximum check size in USD + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_search_response.py b/src/runcaptain/investors/types/investors_search_response.py new file mode 100644 index 0000000..d81f9d2 --- /dev/null +++ b/src/runcaptain/investors/types/investors_search_response.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .investors_search_response_results_item import InvestorsSearchResponseResultsItem + + +class InvestorsSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[InvestorsSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + page: typing.Optional[int] = pydantic.Field(default=None) + """ + Current page number + """ + + page_size: typing.Optional[int] = pydantic.Field(default=None) + """ + Results per page + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_search_response_results_item.py b/src/runcaptain/investors/types/investors_search_response_results_item.py new file mode 100644 index 0000000..325e80a --- /dev/null +++ b/src/runcaptain/investors/types/investors_search_response_results_item.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsSearchResponseResultsItem(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique investor identifier + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + investor_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor type (vc, pe, angel, corporate) + """ + + headquarters: typing.Optional[str] = pydantic.Field(default=None) + """ + Headquarters location + """ + + total_investments: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of investments + """ + + notable_portfolio: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Notable portfolio companies + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_service_providers_deal_response.py b/src/runcaptain/investors/types/investors_service_providers_deal_response.py new file mode 100644 index 0000000..e190083 --- /dev/null +++ b/src/runcaptain/investors/types/investors_service_providers_deal_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsServiceProvidersDealResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + service_providers: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + total_providers: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of deal service providers + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/investors/types/investors_service_providers_response.py b/src/runcaptain/investors/types/investors_service_providers_response.py new file mode 100644 index 0000000..1fead2e --- /dev/null +++ b/src/runcaptain/investors/types/investors_service_providers_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvestorsServiceProvidersResponse(UniversalBaseModel): + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + service_providers: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + total_providers: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of service providers + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/jobs/client.py b/src/runcaptain/jobs/client.py index 9666a4f..99c248e 100644 --- a/src/runcaptain/jobs/client.py +++ b/src/runcaptain/jobs/client.py @@ -5,6 +5,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from ..types.job_cancel_response_v2 import JobCancelResponseV2 +from ..types.job_rollback_response_v2 import JobRollbackResponseV2 from ..types.job_status_response_v2 import JobStatusResponseV2 from .raw_client import AsyncRawJobsClient, RawJobsClient @@ -58,6 +59,7 @@ def get_job_status_v2( Parameters ---------- job_id : str + The job ID returned from an indexing request request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -76,17 +78,17 @@ def get_job_status_v2( key="YOUR_KEY", ) client.jobs.get_job_status_v2( - job_id="job_s3_abc123", + job_id="job_id", ) """ _response = self._raw_client.get_job_status_v2(job_id, request_options=request_options) return _response.data - def cancel_job_v2( + def delete_job_v2( self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> JobCancelResponseV2: """ - Cancel an indexing job. + Cancel and delete an indexing job. Behavior: - If job is pending or running -> transitions to cancelled @@ -95,6 +97,7 @@ def cancel_job_v2( Parameters ---------- job_id : str + The job ID to delete/cancel request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -102,7 +105,7 @@ def cancel_job_v2( Returns ------- JobCancelResponseV2 - Cancel Result + Delete Result Examples -------- @@ -112,11 +115,55 @@ def cancel_job_v2( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.jobs.cancel_job_v2( + client.jobs.delete_job_v2( job_id="job_s3_abc123", ) """ - _response = self._raw_client.cancel_job_v2(job_id, request_options=request_options) + _response = self._raw_client.delete_job_v2(job_id, request_options=request_options) + return _response.data + + def rollback_job_v2( + self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> JobRollbackResponseV2: + """ + Rollback a completed or failed indexing job - removes all indexed files and their associated data. + + ## Behavior + - **Running job**: Returns `409 Conflict` - cancel the job first using `DELETE /v2/jobs/{job_id}` + - **Completed/Failed/Cancelled job**: Deletes all files indexed by this job and returns the list of files removed + - **Not found**: Returns `404` + + ## Use Cases + - Undo a completed indexing job that indexed incorrect data + - Clean up partial data from a failed job + - Remove test data after development/staging indexing runs + + Parameters + ---------- + job_id : str + The job ID to rollback + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + JobRollbackResponseV2 + Rollback completed successfully + + Examples + -------- + from runcaptain import Captain + + client = Captain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + client.jobs.rollback_job_v2( + job_id="abc123xyz-1234567890", + ) + """ + _response = self._raw_client.rollback_job_v2(job_id, request_options=request_options) return _response.data @@ -169,6 +216,7 @@ async def get_job_status_v2( Parameters ---------- job_id : str + The job ID returned from an indexing request request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -192,7 +240,7 @@ async def get_job_status_v2( async def main() -> None: await client.jobs.get_job_status_v2( - job_id="job_s3_abc123", + job_id="job_id", ) @@ -201,11 +249,11 @@ async def main() -> None: _response = await self._raw_client.get_job_status_v2(job_id, request_options=request_options) return _response.data - async def cancel_job_v2( + async def delete_job_v2( self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> JobCancelResponseV2: """ - Cancel an indexing job. + Cancel and delete an indexing job. Behavior: - If job is pending or running -> transitions to cancelled @@ -214,6 +262,7 @@ async def cancel_job_v2( Parameters ---------- job_id : str + The job ID to delete/cancel request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -221,7 +270,7 @@ async def cancel_job_v2( Returns ------- JobCancelResponseV2 - Cancel Result + Delete Result Examples -------- @@ -236,12 +285,64 @@ async def cancel_job_v2( async def main() -> None: - await client.jobs.cancel_job_v2( + await client.jobs.delete_job_v2( job_id="job_s3_abc123", ) asyncio.run(main()) """ - _response = await self._raw_client.cancel_job_v2(job_id, request_options=request_options) + _response = await self._raw_client.delete_job_v2(job_id, request_options=request_options) + return _response.data + + async def rollback_job_v2( + self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> JobRollbackResponseV2: + """ + Rollback a completed or failed indexing job - removes all indexed files and their associated data. + + ## Behavior + - **Running job**: Returns `409 Conflict` - cancel the job first using `DELETE /v2/jobs/{job_id}` + - **Completed/Failed/Cancelled job**: Deletes all files indexed by this job and returns the list of files removed + - **Not found**: Returns `404` + + ## Use Cases + - Undo a completed indexing job that indexed incorrect data + - Clean up partial data from a failed job + - Remove test data after development/staging indexing runs + + Parameters + ---------- + job_id : str + The job ID to rollback + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + JobRollbackResponseV2 + Rollback completed successfully + + Examples + -------- + import asyncio + + from runcaptain import AsyncCaptain + + client = AsyncCaptain( + organization_id="YOUR_ORGANIZATION_ID", + key="YOUR_KEY", + ) + + + async def main() -> None: + await client.jobs.rollback_job_v2( + job_id="abc123xyz-1234567890", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.rollback_job_v2(job_id, request_options=request_options) return _response.data diff --git a/src/runcaptain/jobs/raw_client.py b/src/runcaptain/jobs/raw_client.py index 74cd595..4348acc 100644 --- a/src/runcaptain/jobs/raw_client.py +++ b/src/runcaptain/jobs/raw_client.py @@ -7,11 +7,15 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions +from ..errors.conflict_error import ConflictError from ..errors.not_found_error import NotFoundError from ..types.job_cancel_response_v2 import JobCancelResponseV2 +from ..types.job_rollback_response_v2 import JobRollbackResponseV2 from ..types.job_status_response_v2 import JobStatusResponseV2 +from pydantic import ValidationError class RawJobsClient: @@ -52,6 +56,7 @@ def get_job_status_v2( Parameters ---------- job_id : str + The job ID returned from an indexing request request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -90,13 +95,17 @@ def get_job_status_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def cancel_job_v2( + def delete_job_v2( self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[JobCancelResponseV2]: """ - Cancel an indexing job. + Cancel and delete an indexing job. Behavior: - If job is pending or running -> transitions to cancelled @@ -105,6 +114,7 @@ def cancel_job_v2( Parameters ---------- job_id : str + The job ID to delete/cancel request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -112,11 +122,11 @@ def cancel_job_v2( Returns ------- HttpResponse[JobCancelResponseV2] - Cancel Result + Delete Result """ _response = self._client_wrapper.httpx_client.request( - f"v2/jobs/{jsonable_encoder(job_id)}/cancel", - method="POST", + f"v2/jobs/{jsonable_encoder(job_id)}", + method="DELETE", request_options=request_options, ) try: @@ -132,6 +142,85 @@ def cancel_job_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def rollback_job_v2( + self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[JobRollbackResponseV2]: + """ + Rollback a completed or failed indexing job - removes all indexed files and their associated data. + + ## Behavior + - **Running job**: Returns `409 Conflict` - cancel the job first using `DELETE /v2/jobs/{job_id}` + - **Completed/Failed/Cancelled job**: Deletes all files indexed by this job and returns the list of files removed + - **Not found**: Returns `404` + + ## Use Cases + - Undo a completed indexing job that indexed incorrect data + - Clean up partial data from a failed job + - Remove test data after development/staging indexing runs + + Parameters + ---------- + job_id : str + The job ID to rollback + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[JobRollbackResponseV2] + Rollback completed successfully + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/jobs/{jsonable_encoder(job_id)}/rollback", + method="PATCH", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + JobRollbackResponseV2, + parse_obj_as( + type_=JobRollbackResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -173,6 +262,7 @@ async def get_job_status_v2( Parameters ---------- job_id : str + The job ID returned from an indexing request request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -211,13 +301,17 @@ async def get_job_status_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def cancel_job_v2( + async def delete_job_v2( self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[JobCancelResponseV2]: """ - Cancel an indexing job. + Cancel and delete an indexing job. Behavior: - If job is pending or running -> transitions to cancelled @@ -226,6 +320,7 @@ async def cancel_job_v2( Parameters ---------- job_id : str + The job ID to delete/cancel request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -233,11 +328,11 @@ async def cancel_job_v2( Returns ------- AsyncHttpResponse[JobCancelResponseV2] - Cancel Result + Delete Result """ _response = await self._client_wrapper.httpx_client.request( - f"v2/jobs/{jsonable_encoder(job_id)}/cancel", - method="POST", + f"v2/jobs/{jsonable_encoder(job_id)}", + method="DELETE", request_options=request_options, ) try: @@ -253,4 +348,83 @@ async def cancel_job_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def rollback_job_v2( + self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[JobRollbackResponseV2]: + """ + Rollback a completed or failed indexing job - removes all indexed files and their associated data. + + ## Behavior + - **Running job**: Returns `409 Conflict` - cancel the job first using `DELETE /v2/jobs/{job_id}` + - **Completed/Failed/Cancelled job**: Deletes all files indexed by this job and returns the list of files removed + - **Not found**: Returns `404` + + ## Use Cases + - Undo a completed indexing job that indexed incorrect data + - Clean up partial data from a failed job + - Remove test data after development/staging indexing runs + + Parameters + ---------- + job_id : str + The job ID to rollback + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[JobRollbackResponseV2] + Rollback completed successfully + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/jobs/{jsonable_encoder(job_id)}/rollback", + method="PATCH", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + JobRollbackResponseV2, + parse_obj_as( + type_=JobRollbackResponseV2, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/limited_partners/__init__.py b/src/runcaptain/limited_partners/__init__.py index 5cde020..d578363 100644 --- a/src/runcaptain/limited_partners/__init__.py +++ b/src/runcaptain/limited_partners/__init__.py @@ -2,3 +2,51 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + LpsBioResponse, + LpsBioResponseData, + LpsSearchResponse, + LpsSearchResponseResultsItem, + LpsSearchResponseResultsItemLocation, + ) +_dynamic_imports: typing.Dict[str, str] = { + "LpsBioResponse": ".types", + "LpsBioResponseData": ".types", + "LpsSearchResponse": ".types", + "LpsSearchResponseResultsItem": ".types", + "LpsSearchResponseResultsItemLocation": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "LpsBioResponse", + "LpsBioResponseData", + "LpsSearchResponse", + "LpsSearchResponseResultsItem", + "LpsSearchResponseResultsItemLocation", +] diff --git a/src/runcaptain/limited_partners/client.py b/src/runcaptain/limited_partners/client.py index 51a2dac..6de4f95 100644 --- a/src/runcaptain/limited_partners/client.py +++ b/src/runcaptain/limited_partners/client.py @@ -5,6 +5,8 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawLimitedPartnersClient, RawLimitedPartnersClient +from .types.lps_bio_response import LpsBioResponse +from .types.lps_search_response import LpsSearchResponse class LimitedPartnersClient: @@ -23,22 +25,33 @@ def with_raw_response(self) -> RawLimitedPartnersClient: return self._raw_client def lps_search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: str, + lp_type: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> LpsSearchResponse: """ Search for institutional limited partners including pension funds, endowments, and family offices. Returns matching LP profiles with total commitments and fund relationships. Parameters ---------- + q : str + LP name or keyword (e.g., 'CalPERS') + + lp_type : typing.Optional[str] + Filter by LP type (e.g., 'pension_fund', 'endowment', 'family_office', 'sovereign_wealth', 'insurance') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + LpsSearchResponse Successful response Examples @@ -49,27 +62,29 @@ def lps_search( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.limited_partners.lps_search() + client.limited_partners.lps_search( + q="CalPERS", + limit=10, + ) """ - _response = self._raw_client.lps_search(limit=limit, request_options=request_options) + _response = self._raw_client.lps_search(q=q, lp_type=lp_type, limit=limit, request_options=request_options) return _response.data - def lps_bio( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def lps_bio(self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> LpsBioResponse: """ Get comprehensive limited partner profile including institution type, total assets under management, investment strategy, and notable fund commitments. This is the primary endpoint for LP overview data. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + LpsBioResponse Successful response Examples @@ -87,23 +102,21 @@ def lps_bio( _response = self._raw_client.lps_bio(lp_id, request_options=request_options) return _response.data - def lps_commitments_detailed( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def lps_commitments_detailed(self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. + **Coming Soon** - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -122,21 +135,21 @@ def lps_commitments_detailed( def lps_commitments_aggregates( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. + **Coming Soon** - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -153,23 +166,21 @@ def lps_commitments_aggregates( _response = self._raw_client.lps_commitments_aggregates(lp_id, request_options=request_options) return _response.data - def lps_allocations_target( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def lps_allocations_target(self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. + **Coming Soon** - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -186,23 +197,21 @@ def lps_allocations_target( _response = self._raw_client.lps_allocations_target(lp_id, request_options=request_options) return _response.data - def lps_allocations_actual( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def lps_allocations_actual(self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. + **Coming Soon** - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -221,21 +230,21 @@ def lps_allocations_actual( def lps_commitment_preferences( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. + **Coming Soon** - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -252,23 +261,21 @@ def lps_commitment_preferences( _response = self._raw_client.lps_commitment_preferences(lp_id, request_options=request_options) return _response.data - def lps_service_providers( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def lps_service_providers(self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. + **Coming Soon** - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -285,39 +292,6 @@ def lps_service_providers( _response = self._raw_client.lps_service_providers(lp_id, request_options=request_options) return _response.data - def lps_updates( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to LP profile data. Returns history of changes including new commitments, policy changes, and team updates with timestamps. - - Parameters - ---------- - lp_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.limited_partners.lps_updates( - lp_id="calpers", - ) - """ - _response = self._raw_client.lps_updates(lp_id, request_options=request_options) - return _response.data - class AsyncLimitedPartnersClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -335,22 +309,33 @@ def with_raw_response(self) -> AsyncRawLimitedPartnersClient: return self._raw_client async def lps_search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: str, + lp_type: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> LpsSearchResponse: """ Search for institutional limited partners including pension funds, endowments, and family offices. Returns matching LP profiles with total commitments and fund relationships. Parameters ---------- + q : str + LP name or keyword (e.g., 'CalPERS') + + lp_type : typing.Optional[str] + Filter by LP type (e.g., 'pension_fund', 'endowment', 'family_office', 'sovereign_wealth', 'insurance') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + LpsSearchResponse Successful response Examples @@ -366,30 +351,34 @@ async def lps_search( async def main() -> None: - await client.limited_partners.lps_search() + await client.limited_partners.lps_search( + q="CalPERS", + limit=10, + ) asyncio.run(main()) """ - _response = await self._raw_client.lps_search(limit=limit, request_options=request_options) + _response = await self._raw_client.lps_search( + q=q, lp_type=lp_type, limit=limit, request_options=request_options + ) return _response.data - async def lps_bio( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + async def lps_bio(self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> LpsBioResponse: """ Get comprehensive limited partner profile including institution type, total assets under management, investment strategy, and notable fund commitments. This is the primary endpoint for LP overview data. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + LpsBioResponse Successful response Examples @@ -417,21 +406,21 @@ async def main() -> None: async def lps_commitments_detailed( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. + **Coming Soon** - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -458,21 +447,21 @@ async def main() -> None: async def lps_commitments_aggregates( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. + **Coming Soon** - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -499,21 +488,21 @@ async def main() -> None: async def lps_allocations_target( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. + **Coming Soon** - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -540,21 +529,21 @@ async def main() -> None: async def lps_allocations_actual( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. + **Coming Soon** - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -581,21 +570,21 @@ async def main() -> None: async def lps_commitment_preferences( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. + **Coming Soon** - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -622,21 +611,21 @@ async def main() -> None: async def lps_service_providers( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. + **Coming Soon** - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -660,44 +649,3 @@ async def main() -> None: """ _response = await self._raw_client.lps_service_providers(lp_id, request_options=request_options) return _response.data - - async def lps_updates( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to LP profile data. Returns history of changes including new commitments, policy changes, and team updates with timestamps. - - Parameters - ---------- - lp_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.limited_partners.lps_updates( - lp_id="calpers", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.lps_updates(lp_id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/limited_partners/raw_client.py b/src/runcaptain/limited_partners/raw_client.py index 696dff5..c9934fd 100644 --- a/src/runcaptain/limited_partners/raw_client.py +++ b/src/runcaptain/limited_partners/raw_client.py @@ -7,11 +7,15 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.lps_bio_response import LpsBioResponse +from .types.lps_search_response import LpsSearchResponse +from pydantic import ValidationError class RawLimitedPartnersClient: @@ -19,28 +23,41 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def lps_search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: str, + lp_type: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[LpsSearchResponse]: """ Search for institutional limited partners including pension funds, endowments, and family offices. Returns matching LP profiles with total commitments and fund relationships. Parameters ---------- + q : str + LP name or keyword (e.g., 'CalPERS') + + lp_type : typing.Optional[str] + Filter by LP type (e.g., 'pension_fund', 'endowment', 'family_office', 'sovereign_wealth', 'insurance') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[LpsSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/limited-partners/search", method="GET", params={ + "q": q, + "lp_type": lp_type, "limit": limit, }, request_options=request_options, @@ -48,9 +65,9 @@ def lps_search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + LpsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=LpsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -69,24 +86,29 @@ def lps_search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_bio( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[LpsBioResponse]: """ Get comprehensive limited partner profile including institution type, total assets under management, investment strategy, and notable fund commitments. This is the primary endpoint for LP overview data. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[LpsBioResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -97,9 +119,9 @@ def lps_bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + LpsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=LpsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -129,25 +151,29 @@ def lps_bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_commitments_detailed( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. + **Coming Soon** - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/commitments/detailed", @@ -156,14 +182,7 @@ def lps_commitments_detailed( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -200,25 +219,29 @@ def lps_commitments_detailed( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_commitments_aggregates( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. + **Coming Soon** - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/commitments/aggregates", @@ -227,14 +250,7 @@ def lps_commitments_aggregates( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -271,25 +287,29 @@ def lps_commitments_aggregates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_allocations_target( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. + **Coming Soon** - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/allocations/target", @@ -298,14 +318,7 @@ def lps_allocations_target( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -342,25 +355,29 @@ def lps_allocations_target( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_allocations_actual( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. + **Coming Soon** - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/allocations/actual", @@ -369,14 +386,7 @@ def lps_allocations_actual( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -413,25 +423,29 @@ def lps_allocations_actual( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_commitment_preferences( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. + **Coming Soon** - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/commitment-preferences", @@ -440,14 +454,7 @@ def lps_commitment_preferences( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -484,25 +491,29 @@ def lps_commitment_preferences( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def lps_service_providers( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. + **Coming Soon** - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/service-providers", @@ -511,14 +522,7 @@ def lps_service_providers( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -555,66 +559,10 @@ def lps_service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def lps_updates( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to LP profile data. Returns history of changes including new commitments, policy changes, and team updates with timestamps. - - Parameters - ---------- - lp_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -623,28 +571,41 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def lps_search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: str, + lp_type: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[LpsSearchResponse]: """ Search for institutional limited partners including pension funds, endowments, and family offices. Returns matching LP profiles with total commitments and fund relationships. Parameters ---------- + q : str + LP name or keyword (e.g., 'CalPERS') + + lp_type : typing.Optional[str] + Filter by LP type (e.g., 'pension_fund', 'endowment', 'family_office', 'sovereign_wealth', 'insurance') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[LpsSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/limited-partners/search", method="GET", params={ + "q": q, + "lp_type": lp_type, "limit": limit, }, request_options=request_options, @@ -652,9 +613,9 @@ async def lps_search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + LpsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=LpsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -673,24 +634,29 @@ async def lps_search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_bio( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[LpsBioResponse]: """ Get comprehensive limited partner profile including institution type, total assets under management, investment strategy, and notable fund commitments. This is the primary endpoint for LP overview data. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[LpsBioResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -701,9 +667,9 @@ async def lps_bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + LpsBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=LpsBioResponse, # type: ignore object_=_response.json(), ), ) @@ -733,25 +699,29 @@ async def lps_bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_commitments_detailed( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. + **Coming Soon** - Get detailed fund commitments including specific fund names, commitment amounts, vintage years, and commitment status. Returns complete LP portfolio. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/commitments/detailed", @@ -760,14 +730,7 @@ async def lps_commitments_detailed( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -804,25 +767,29 @@ async def lps_commitments_detailed( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_commitments_aggregates( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. + **Coming Soon** - Get aggregated commitment statistics including total commitments by vintage year, fund type, and geography. Returns high-level allocation summary. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/commitments/aggregates", @@ -831,14 +798,7 @@ async def lps_commitments_aggregates( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -875,25 +835,29 @@ async def lps_commitments_aggregates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_allocations_target( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. + **Coming Soon** - Get target allocation percentages by asset class, geography, and strategy. Returns investment policy guidelines and target portfolio mix. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/allocations/target", @@ -902,14 +866,7 @@ async def lps_allocations_target( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -946,25 +903,29 @@ async def lps_allocations_target( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_allocations_actual( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. + **Coming Soon** - Get actual current allocation percentages by asset class, geography, and strategy. Returns real portfolio composition and compare to targets. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/allocations/actual", @@ -973,14 +934,7 @@ async def lps_allocations_actual( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1017,25 +971,29 @@ async def lps_allocations_actual( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_commitment_preferences( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. + **Coming Soon** - Get commitment preferences including preferred fund sizes, vintage year focus, and investment stage preferences. Useful for GP fundraising targeting. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/commitment-preferences", @@ -1044,14 +1002,7 @@ async def lps_commitment_preferences( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1088,25 +1039,29 @@ async def lps_commitment_preferences( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def lps_service_providers( self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. + **Coming Soon** - Get service providers used by the LP including consultants, custodians, and legal advisors. Returns firm names and service types. Parameters ---------- lp_id : str + LP entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/service-providers", @@ -1115,14 +1070,7 @@ async def lps_service_providers( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1159,64 +1107,8 @@ async def lps_service_providers( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def lps_updates( - self, lp_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to LP profile data. Returns history of changes including new commitments, policy changes, and team updates with timestamps. - - Parameters - ---------- - lp_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/limited-partners/{jsonable_encoder(lp_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/limited_partners/types/__init__.py b/src/runcaptain/limited_partners/types/__init__.py new file mode 100644 index 0000000..02ac412 --- /dev/null +++ b/src/runcaptain/limited_partners/types/__init__.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .lps_bio_response import LpsBioResponse + from .lps_bio_response_data import LpsBioResponseData + from .lps_search_response import LpsSearchResponse + from .lps_search_response_results_item import LpsSearchResponseResultsItem + from .lps_search_response_results_item_location import LpsSearchResponseResultsItemLocation +_dynamic_imports: typing.Dict[str, str] = { + "LpsBioResponse": ".lps_bio_response", + "LpsBioResponseData": ".lps_bio_response_data", + "LpsSearchResponse": ".lps_search_response", + "LpsSearchResponseResultsItem": ".lps_search_response_results_item", + "LpsSearchResponseResultsItemLocation": ".lps_search_response_results_item_location", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "LpsBioResponse", + "LpsBioResponseData", + "LpsSearchResponse", + "LpsSearchResponseResultsItem", + "LpsSearchResponseResultsItemLocation", +] diff --git a/src/runcaptain/limited_partners/types/lps_bio_response.py b/src/runcaptain/limited_partners/types/lps_bio_response.py new file mode 100644 index 0000000..f232e05 --- /dev/null +++ b/src/runcaptain/limited_partners/types/lps_bio_response.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .lps_bio_response_data import LpsBioResponseData + + +class LpsBioResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + LP entity ID + """ + + identifier: typing.Optional[str] = pydantic.Field(default=None) + """ + LP identifier + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Always 'limited_partner' + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + LP display name + """ + + data: typing.Optional[LpsBioResponseData] = None + note: typing.Optional[str] = pydantic.Field(default=None) + """ + Data availability note + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + LP industry classification + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + LP description/summary + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/limited_partners/types/lps_bio_response_data.py b/src/runcaptain/limited_partners/types/lps_bio_response_data.py new file mode 100644 index 0000000..343b62f --- /dev/null +++ b/src/runcaptain/limited_partners/types/lps_bio_response_data.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class LpsBioResponseData(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + LP name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + LP industry classification + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + LP description/summary + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/limited_partners/types/lps_search_response.py b/src/runcaptain/limited_partners/types/lps_search_response.py new file mode 100644 index 0000000..5e68f20 --- /dev/null +++ b/src/runcaptain/limited_partners/types/lps_search_response.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .lps_search_response_results_item import LpsSearchResponseResultsItem + + +class LpsSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[LpsSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum results requested + """ + + note: typing.Optional[str] = pydantic.Field(default=None) + """ + Data availability note + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/limited_partners/types/lps_search_response_results_item.py b/src/runcaptain/limited_partners/types/lps_search_response_results_item.py new file mode 100644 index 0000000..050b21f --- /dev/null +++ b/src/runcaptain/limited_partners/types/lps_search_response_results_item.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .lps_search_response_results_item_location import LpsSearchResponseResultsItemLocation + + +class LpsSearchResponseResultsItem(UniversalBaseModel): + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + LP name + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Industry classification + """ + + type: typing.Optional[str] = pydantic.Field(default=None) + """ + LP type + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Data source + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + location: typing.Optional[LpsSearchResponseResultsItemLocation] = pydantic.Field(default=None) + """ + HQ location + """ + + note: typing.Optional[str] = pydantic.Field(default=None) + """ + Data availability note + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/limited_partners/types/lps_search_response_results_item_location.py b/src/runcaptain/limited_partners/types/lps_search_response_results_item_location.py new file mode 100644 index 0000000..5846c63 --- /dev/null +++ b/src/runcaptain/limited_partners/types/lps_search_response_results_item_location.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class LpsSearchResponseResultsItemLocation(UniversalBaseModel): + """ + HQ location + """ + + locality: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/patents/__init__.py b/src/runcaptain/patents/__init__.py index 5cde020..f2073e9 100644 --- a/src/runcaptain/patents/__init__.py +++ b/src/runcaptain/patents/__init__.py @@ -2,3 +2,51 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + PatentsGetByIdResponse, + PatentsGetFileResponse, + PatentsSearchResponse, + PatentsSearchResponsePatentsItem, + PatentsSearchResponseResultsItem, + ) +_dynamic_imports: typing.Dict[str, str] = { + "PatentsGetByIdResponse": ".types", + "PatentsGetFileResponse": ".types", + "PatentsSearchResponse": ".types", + "PatentsSearchResponsePatentsItem": ".types", + "PatentsSearchResponseResultsItem": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "PatentsGetByIdResponse", + "PatentsGetFileResponse", + "PatentsSearchResponse", + "PatentsSearchResponsePatentsItem", + "PatentsSearchResponseResultsItem", +] diff --git a/src/runcaptain/patents/client.py b/src/runcaptain/patents/client.py index e3bc0f6..c8972d6 100644 --- a/src/runcaptain/patents/client.py +++ b/src/runcaptain/patents/client.py @@ -5,6 +5,9 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawPatentsClient, RawPatentsClient +from .types.patents_get_by_id_response import PatentsGetByIdResponse +from .types.patents_get_file_response import PatentsGetFileResponse +from .types.patents_search_response import PatentsSearchResponse class PatentsClient: @@ -23,22 +26,33 @@ def with_raw_response(self) -> RawPatentsClient: return self._raw_client def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: str, + assignee: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PatentsSearchResponse: """ Search for patents by title, inventor, assignee, or classification. Returns matching patents with patent numbers, titles, filing dates, and status. Parameters ---------- + q : str + Patent keyword, assignee, or inventor name + + assignee : typing.Optional[str] + Filter by assignee (company) + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PatentsSearchResponse Successful response Examples @@ -49,27 +63,33 @@ def search( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.patents.search() + client.patents.search( + q="machine learning", + limit=10, + ) """ - _response = self._raw_client.search(limit=limit, request_options=request_options) + _response = self._raw_client.search(q=q, assignee=assignee, limit=limit, request_options=request_options) return _response.data - def get_by_id( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def get_by_id(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> PatentsGetByIdResponse: """ - Get detailed patent information including abstract, claims, inventors, citations, and prosecution history. Returns comprehensive patent profile. + Get patent details by patent number. + + Uses Google Patents search engine to find structured patent data including title, abstract, assignee, inventors, filing date, and publication date. + + Returns a direct link to the patent on Google Patents. Parameters ---------- id : str + Patent ID or number request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PatentsGetByIdResponse Successful response Examples @@ -89,20 +109,21 @@ def get_by_id( def get_file( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> PatentsGetFileResponse: """ Download patent file wrapper or PDF document. Returns patent documentation and prosecution history files. Parameters ---------- entity_id : str + Patent entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PatentsGetFileResponse Successful response Examples @@ -137,22 +158,33 @@ def with_raw_response(self) -> AsyncRawPatentsClient: return self._raw_client async def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + self, + *, + q: str, + assignee: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PatentsSearchResponse: """ Search for patents by title, inventor, assignee, or classification. Returns matching patents with patent numbers, titles, filing dates, and status. Parameters ---------- + q : str + Patent keyword, assignee, or inventor name + + assignee : typing.Optional[str] + Filter by assignee (company) + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PatentsSearchResponse Successful response Examples @@ -168,30 +200,38 @@ async def search( async def main() -> None: - await client.patents.search() + await client.patents.search( + q="machine learning", + limit=10, + ) asyncio.run(main()) """ - _response = await self._raw_client.search(limit=limit, request_options=request_options) + _response = await self._raw_client.search(q=q, assignee=assignee, limit=limit, request_options=request_options) return _response.data async def get_by_id( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> PatentsGetByIdResponse: """ - Get detailed patent information including abstract, claims, inventors, citations, and prosecution history. Returns comprehensive patent profile. + Get patent details by patent number. + + Uses Google Patents search engine to find structured patent data including title, abstract, assignee, inventors, filing date, and publication date. + + Returns a direct link to the patent on Google Patents. Parameters ---------- id : str + Patent ID or number request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PatentsGetByIdResponse Successful response Examples @@ -219,20 +259,21 @@ async def main() -> None: async def get_file( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> PatentsGetFileResponse: """ Download patent file wrapper or PDF document. Returns patent documentation and prosecution history files. Parameters ---------- entity_id : str + Patent entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PatentsGetFileResponse Successful response Examples diff --git a/src/runcaptain/patents/raw_client.py b/src/runcaptain/patents/raw_client.py index aea446d..0978dc4 100644 --- a/src/runcaptain/patents/raw_client.py +++ b/src/runcaptain/patents/raw_client.py @@ -7,11 +7,16 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.patents_get_by_id_response import PatentsGetByIdResponse +from .types.patents_get_file_response import PatentsGetFileResponse +from .types.patents_search_response import PatentsSearchResponse +from pydantic import ValidationError class RawPatentsClient: @@ -19,28 +24,41 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: str, + assignee: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PatentsSearchResponse]: """ Search for patents by title, inventor, assignee, or classification. Returns matching patents with patent numbers, titles, filing dates, and status. Parameters ---------- + q : str + Patent keyword, assignee, or inventor name + + assignee : typing.Optional[str] + Filter by assignee (company) + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[PatentsSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/patents/search", method="GET", params={ + "q": q, + "assignee": assignee, "limit": limit, }, request_options=request_options, @@ -48,9 +66,9 @@ def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PatentsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PatentsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -69,24 +87,33 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def get_by_id( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[PatentsGetByIdResponse]: """ - Get detailed patent information including abstract, claims, inventors, citations, and prosecution history. Returns comprehensive patent profile. + Get patent details by patent number. + + Uses Google Patents search engine to find structured patent data including title, abstract, assignee, inventors, filing date, and publication date. + + Returns a direct link to the patent on Google Patents. Parameters ---------- id : str + Patent ID or number request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[PatentsGetByIdResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -97,9 +124,9 @@ def get_by_id( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PatentsGetByIdResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PatentsGetByIdResponse, # type: ignore object_=_response.json(), ), ) @@ -129,24 +156,29 @@ def get_by_id( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def get_file( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[PatentsGetFileResponse]: """ Download patent file wrapper or PDF document. Returns patent documentation and prosecution history files. Parameters ---------- entity_id : str + Patent entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[PatentsGetFileResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -157,9 +189,9 @@ def get_file( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PatentsGetFileResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PatentsGetFileResponse, # type: ignore object_=_response.json(), ), ) @@ -200,6 +232,10 @@ def get_file( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -208,28 +244,41 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def search( - self, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + self, + *, + q: str, + assignee: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PatentsSearchResponse]: """ Search for patents by title, inventor, assignee, or classification. Returns matching patents with patent numbers, titles, filing dates, and status. Parameters ---------- + q : str + Patent keyword, assignee, or inventor name + + assignee : typing.Optional[str] + Filter by assignee (company) + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[PatentsSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/patents/search", method="GET", params={ + "q": q, + "assignee": assignee, "limit": limit, }, request_options=request_options, @@ -237,9 +286,9 @@ async def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PatentsSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PatentsSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -258,24 +307,33 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def get_by_id( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[PatentsGetByIdResponse]: """ - Get detailed patent information including abstract, claims, inventors, citations, and prosecution history. Returns comprehensive patent profile. + Get patent details by patent number. + + Uses Google Patents search engine to find structured patent data including title, abstract, assignee, inventors, filing date, and publication date. + + Returns a direct link to the patent on Google Patents. Parameters ---------- id : str + Patent ID or number request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[PatentsGetByIdResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -286,9 +344,9 @@ async def get_by_id( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PatentsGetByIdResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PatentsGetByIdResponse, # type: ignore object_=_response.json(), ), ) @@ -318,24 +376,29 @@ async def get_by_id( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def get_file( self, entity_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[PatentsGetFileResponse]: """ Download patent file wrapper or PDF document. Returns patent documentation and prosecution history files. Parameters ---------- entity_id : str + Patent entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[PatentsGetFileResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -346,9 +409,9 @@ async def get_file( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PatentsGetFileResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PatentsGetFileResponse, # type: ignore object_=_response.json(), ), ) @@ -389,4 +452,8 @@ async def get_file( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/patents/types/__init__.py b/src/runcaptain/patents/types/__init__.py new file mode 100644 index 0000000..edc0393 --- /dev/null +++ b/src/runcaptain/patents/types/__init__.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .patents_get_by_id_response import PatentsGetByIdResponse + from .patents_get_file_response import PatentsGetFileResponse + from .patents_search_response import PatentsSearchResponse + from .patents_search_response_patents_item import PatentsSearchResponsePatentsItem + from .patents_search_response_results_item import PatentsSearchResponseResultsItem +_dynamic_imports: typing.Dict[str, str] = { + "PatentsGetByIdResponse": ".patents_get_by_id_response", + "PatentsGetFileResponse": ".patents_get_file_response", + "PatentsSearchResponse": ".patents_search_response", + "PatentsSearchResponsePatentsItem": ".patents_search_response_patents_item", + "PatentsSearchResponseResultsItem": ".patents_search_response_results_item", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "PatentsGetByIdResponse", + "PatentsGetFileResponse", + "PatentsSearchResponse", + "PatentsSearchResponsePatentsItem", + "PatentsSearchResponseResultsItem", +] diff --git a/src/runcaptain/patents/types/patents_get_by_id_response.py b/src/runcaptain/patents/types/patents_get_by_id_response.py new file mode 100644 index 0000000..10d909a --- /dev/null +++ b/src/runcaptain/patents/types/patents_get_by_id_response.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PatentsGetByIdResponse(UniversalBaseModel): + patent_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent number + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent title + """ + + abstract: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent abstract + """ + + filing_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Filing date + """ + + grant_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Grant date + """ + + assignee: typing.Optional[str] = pydantic.Field(default=None) + """ + Assignee/owner + """ + + inventors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Inventor names + """ + + claims: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Patent claims + """ + + classifications: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Patent classifications + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/patents/types/patents_get_file_response.py b/src/runcaptain/patents/types/patents_get_file_response.py new file mode 100644 index 0000000..a401116 --- /dev/null +++ b/src/runcaptain/patents/types/patents_get_file_response.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PatentsGetFileResponse(UniversalBaseModel): + """ + Patent document file data + """ + + patent_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent number + """ + + file_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Document file URL + """ + + file_type: typing.Optional[str] = pydantic.Field(default=None) + """ + File type (pdf, etc.) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/patents/types/patents_search_response.py b/src/runcaptain/patents/types/patents_search_response.py new file mode 100644 index 0000000..728682c --- /dev/null +++ b/src/runcaptain/patents/types/patents_search_response.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .patents_search_response_patents_item import PatentsSearchResponsePatentsItem +from .patents_search_response_results_item import PatentsSearchResponseResultsItem + + +class PatentsSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[PatentsSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + page: typing.Optional[int] = pydantic.Field(default=None) + """ + Current page number + """ + + page_size: typing.Optional[int] = pydantic.Field(default=None) + """ + Results per page + """ + + patents: typing.Optional[typing.List[PatentsSearchResponsePatentsItem]] = pydantic.Field(default=None) + """ + Matching patents (alias for results) + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Error message if search failed + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/patents/types/patents_search_response_patents_item.py b/src/runcaptain/patents/types/patents_search_response_patents_item.py new file mode 100644 index 0000000..994060c --- /dev/null +++ b/src/runcaptain/patents/types/patents_search_response_patents_item.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PatentsSearchResponsePatentsItem(UniversalBaseModel): + patent_id: typing.Optional[str] = None + title: typing.Optional[str] = None + abstract: typing.Optional[str] = None + filing_date: typing.Optional[str] = None + grant_date: typing.Optional[str] = None + assignee: typing.Optional[str] = None + patent_number: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent number (e.g. US11475380B2) + """ + + publication_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Publication date + """ + + inventors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Inventor names + """ + + patent_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Google Patents URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/patents/types/patents_search_response_results_item.py b/src/runcaptain/patents/types/patents_search_response_results_item.py new file mode 100644 index 0000000..419aad8 --- /dev/null +++ b/src/runcaptain/patents/types/patents_search_response_results_item.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PatentsSearchResponseResultsItem(UniversalBaseModel): + patent_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent number + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent title + """ + + abstract: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent abstract + """ + + filing_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Filing date (YYYY-MM-DD) + """ + + grant_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Grant date (YYYY-MM-DD) + """ + + assignee: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent assignee/owner + """ + + patent_number: typing.Optional[str] = pydantic.Field(default=None) + """ + Patent number (e.g. US11475380B2) + """ + + publication_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Publication date + """ + + inventors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Inventor names + """ + + patent_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Google Patents URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/people/__init__.py b/src/runcaptain/people/__init__.py index 5cde020..d8e5be1 100644 --- a/src/runcaptain/people/__init__.py +++ b/src/runcaptain/people/__init__.py @@ -2,3 +2,51 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + PeopleBioResponse, + PeopleBioResponseCurrentCompany, + PeopleBioResponseLocation, + PeopleSearchResponse, + PeopleSearchResponseResultsItem, + ) +_dynamic_imports: typing.Dict[str, str] = { + "PeopleBioResponse": ".types", + "PeopleBioResponseCurrentCompany": ".types", + "PeopleBioResponseLocation": ".types", + "PeopleSearchResponse": ".types", + "PeopleSearchResponseResultsItem": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "PeopleBioResponse", + "PeopleBioResponseCurrentCompany", + "PeopleBioResponseLocation", + "PeopleSearchResponse", + "PeopleSearchResponseResultsItem", +] diff --git a/src/runcaptain/people/client.py b/src/runcaptain/people/client.py index a260062..2f45ee5 100644 --- a/src/runcaptain/people/client.py +++ b/src/runcaptain/people/client.py @@ -5,6 +5,8 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawPeopleClient, RawPeopleClient +from .types.people_bio_response import PeopleBioResponse +from .types.people_search_response import PeopleSearchResponse class PeopleClient: @@ -25,97 +27,54 @@ def with_raw_response(self) -> RawPeopleClient: def search( self, *, + q: str, + company: typing.Optional[str] = None, title: typing.Optional[str] = None, location: typing.Optional[str] = None, limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> PeopleSearchResponse: """ - Search for professionals by name, company, title, or location. Returns matching profiles with current position, company, and LinkedIn URL. Use this to find person entity IDs for detailed lookups. + Search for people by name, company, title, or natural language query. Returns LinkedIn profiles with rich metadata including professional headline, location, bio excerpt, follower count, and school. - Parameters - ---------- - title : typing.Optional[str] - Filter by job title + Use the returned `entity_id` with `/bio`, `/contact`, or `/education-work` to get full enrichment data. - location : typing.Optional[str] - Filter by location + **Pagination:** Use `offset` and `limit` to page through results. Check `has_more` in the response to determine if more pages are available. - limit : typing.Optional[int] - Maximum results - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.people.search() - """ - _response = self._raw_client.search( - title=title, location=location, limit=limit, request_options=request_options - ) - return _response.data + **Large result sets:** Supports up to 500 results per request. For requests exceeding 100 results, the API automatically expands the search with query variations to discover more profiles, then deduplicates by LinkedIn URL. - def bio( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get comprehensive person profile including headline, summary, current company, location, and social profiles. This is the primary endpoint for person overview data. + **Examples:** + - `?q=engineers at Anthropic in San Francisco&limit=20` + - `?q=Sam Altman&limit=1` + - `?q=senior data scientists&company=Stripe&location=New York&limit=50&offset=0` Parameters ---------- - person_id : str + q : str + Person name or search query - request_options : typing.Optional[RequestOptions] - Request-specific configuration. + company : typing.Optional[str] + Filter by current company - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain + title : typing.Optional[str] + Filter by job title - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.people.bio( - person_id="ody_pe_019cb8ac-f123-749e-a75e-e5f669g53c05", - ) - """ - _response = self._raw_client.bio(person_id, request_options=request_options) - return _response.data + location : typing.Optional[str] + Filter by location - def contact( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get contact information including email addresses, phone numbers, and social media profiles. Returns verified contact details for outreach. + limit : typing.Optional[int] + Maximum results per page (1-500). For limits above 100, query expansion is used automatically. - Parameters - ---------- - person_id : str + offset : typing.Optional[int] + Number of results to skip for pagination. Use with limit to page through results. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PeopleSearchResponse Successful response Examples @@ -126,62 +85,38 @@ def contact( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.people.contact( - person_id="linkedin.com/in/samaltman", + client.people.search( + q="engineers at Anthropic in San Francisco", + limit=5, + offset=0, ) """ - _response = self._raw_client.contact(person_id, request_options=request_options) - return _response.data - - def education_work( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete professional history including work experience and education. Returns job positions with companies, titles, dates, and degree information with schools and majors. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.people.education_work( - person_id="linkedin.com/in/samaltman", + _response = self._raw_client.search( + q=q, + company=company, + title=title, + location=location, + limit=limit, + offset=offset, + request_options=request_options, ) - """ - _response = self._raw_client.education_work(person_id, request_options=request_options) return _response.data - def updates( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def bio(self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> PeopleBioResponse: """ - Get changelog of updates to person profile data. Returns history of career moves, title changes, and profile updates with timestamps. + Get complete person profile including bio, contact information (emails, phones, social profiles), work history (all positions with companies, titles, dates), and education (schools, degrees, fields of study). One API call returns everything. Use the entity_id from /people/search results. Parameters ---------- person_id : str + Person entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PeopleBioResponse Successful response Examples @@ -192,11 +127,11 @@ def updates( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.people.updates( - person_id="linkedin.com/in/samaltman", + client.people.bio( + person_id="019d886b-c9e6-745a-b91f-d7ece19a914c", ) """ - _response = self._raw_client.updates(person_id, request_options=request_options) + _response = self._raw_client.bio(person_id, request_options=request_options) return _response.data @@ -218,16 +153,36 @@ def with_raw_response(self) -> AsyncRawPeopleClient: async def search( self, *, + q: str, + company: typing.Optional[str] = None, title: typing.Optional[str] = None, location: typing.Optional[str] = None, limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> PeopleSearchResponse: """ - Search for professionals by name, company, title, or location. Returns matching profiles with current position, company, and LinkedIn URL. Use this to find person entity IDs for detailed lookups. + Search for people by name, company, title, or natural language query. Returns LinkedIn profiles with rich metadata including professional headline, location, bio excerpt, follower count, and school. + + Use the returned `entity_id` with `/bio`, `/contact`, or `/education-work` to get full enrichment data. + + **Pagination:** Use `offset` and `limit` to page through results. Check `has_more` in the response to determine if more pages are available. + + **Large result sets:** Supports up to 500 results per request. For requests exceeding 100 results, the API automatically expands the search with query variations to discover more profiles, then deduplicates by LinkedIn URL. + + **Examples:** + - `?q=engineers at Anthropic in San Francisco&limit=20` + - `?q=Sam Altman&limit=1` + - `?q=senior data scientists&company=Stripe&location=New York&limit=50&offset=0` Parameters ---------- + q : str + Person name or search query + + company : typing.Optional[str] + Filter by current company + title : typing.Optional[str] Filter by job title @@ -235,14 +190,17 @@ async def search( Filter by location limit : typing.Optional[int] - Maximum results + Maximum results per page (1-500). For limits above 100, query expansion is used automatically. + + offset : typing.Optional[int] + Number of results to skip for pagination. Use with limit to page through results. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PeopleSearchResponse Successful response Examples @@ -258,32 +216,43 @@ async def search( async def main() -> None: - await client.people.search() + await client.people.search( + q="engineers at Anthropic in San Francisco", + limit=5, + offset=0, + ) asyncio.run(main()) """ _response = await self._raw_client.search( - title=title, location=location, limit=limit, request_options=request_options + q=q, + company=company, + title=title, + location=location, + limit=limit, + offset=offset, + request_options=request_options, ) return _response.data async def bio( self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> PeopleBioResponse: """ - Get comprehensive person profile including headline, summary, current company, location, and social profiles. This is the primary endpoint for person overview data. + Get complete person profile including bio, contact information (emails, phones, social profiles), work history (all positions with companies, titles, dates), and education (schools, degrees, fields of study). One API call returns everything. Use the entity_id from /people/search results. Parameters ---------- person_id : str + Person entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + PeopleBioResponse Successful response Examples @@ -300,7 +269,7 @@ async def bio( async def main() -> None: await client.people.bio( - person_id="ody_pe_019cb8ac-f123-749e-a75e-e5f669g53c05", + person_id="019d886b-c9e6-745a-b91f-d7ece19a914c", ) @@ -308,126 +277,3 @@ async def main() -> None: """ _response = await self._raw_client.bio(person_id, request_options=request_options) return _response.data - - async def contact( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get contact information including email addresses, phone numbers, and social media profiles. Returns verified contact details for outreach. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.people.contact( - person_id="linkedin.com/in/samaltman", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.contact(person_id, request_options=request_options) - return _response.data - - async def education_work( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get complete professional history including work experience and education. Returns job positions with companies, titles, dates, and degree information with schools and majors. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.people.education_work( - person_id="linkedin.com/in/samaltman", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.education_work(person_id, request_options=request_options) - return _response.data - - async def updates( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to person profile data. Returns history of career moves, title changes, and profile updates with timestamps. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.people.updates( - person_id="linkedin.com/in/samaltman", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.updates(person_id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/people/raw_client.py b/src/runcaptain/people/raw_client.py index 81463ec..17dbe14 100644 --- a/src/runcaptain/people/raw_client.py +++ b/src/runcaptain/people/raw_client.py @@ -7,10 +7,14 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.unauthorized_error import UnauthorizedError +from .types.people_bio_response import PeopleBioResponse +from .types.people_search_response import PeopleSearchResponse +from pydantic import ValidationError class RawPeopleClient: @@ -20,16 +24,36 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def search( self, *, + q: str, + company: typing.Optional[str] = None, title: typing.Optional[str] = None, location: typing.Optional[str] = None, limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[PeopleSearchResponse]: """ - Search for professionals by name, company, title, or location. Returns matching profiles with current position, company, and LinkedIn URL. Use this to find person entity IDs for detailed lookups. + Search for people by name, company, title, or natural language query. Returns LinkedIn profiles with rich metadata including professional headline, location, bio excerpt, follower count, and school. + + Use the returned `entity_id` with `/bio`, `/contact`, or `/education-work` to get full enrichment data. + + **Pagination:** Use `offset` and `limit` to page through results. Check `has_more` in the response to determine if more pages are available. + + **Large result sets:** Supports up to 500 results per request. For requests exceeding 100 results, the API automatically expands the search with query variations to discover more profiles, then deduplicates by LinkedIn URL. + + **Examples:** + - `?q=engineers at Anthropic in San Francisco&limit=20` + - `?q=Sam Altman&limit=1` + - `?q=senior data scientists&company=Stripe&location=New York&limit=50&offset=0` Parameters ---------- + q : str + Person name or search query + + company : typing.Optional[str] + Filter by current company + title : typing.Optional[str] Filter by job title @@ -37,32 +61,38 @@ def search( Filter by location limit : typing.Optional[int] - Maximum results + Maximum results per page (1-500). For limits above 100, query expansion is used automatically. + + offset : typing.Optional[int] + Number of results to skip for pagination. Use with limit to page through results. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[PeopleSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/people/search", method="GET", params={ + "q": q, + "company": company, "title": title, "location": location, "limit": limit, + "offset": offset, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PeopleSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PeopleSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -81,24 +111,29 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def bio( self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[PeopleBioResponse]: """ - Get comprehensive person profile including headline, summary, current company, location, and social profiles. This is the primary endpoint for person overview data. + Get complete person profile including bio, contact information (emails, phones, social profiles), work history (all positions with companies, titles, dates), and education (schools, degrees, fields of study). One API call returns everything. Use the entity_id from /people/search results. Parameters ---------- person_id : str + Person entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[PeopleBioResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -109,189 +144,9 @@ def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def contact( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get contact information including email addresses, phone numbers, and social media profiles. Returns verified contact details for outreach. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/people/{jsonable_encoder(person_id)}/contact", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def education_work( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete professional history including work experience and education. Returns job positions with companies, titles, dates, and degree information with schools and majors. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/people/{jsonable_encoder(person_id)}/education-work", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def updates( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to person profile data. Returns history of career moves, title changes, and profile updates with timestamps. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/people/{jsonable_encoder(person_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], + PeopleBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PeopleBioResponse, # type: ignore object_=_response.json(), ), ) @@ -321,6 +176,10 @@ def updates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -331,16 +190,36 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def search( self, *, + q: str, + company: typing.Optional[str] = None, title: typing.Optional[str] = None, location: typing.Optional[str] = None, limit: typing.Optional[int] = None, + offset: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[PeopleSearchResponse]: """ - Search for professionals by name, company, title, or location. Returns matching profiles with current position, company, and LinkedIn URL. Use this to find person entity IDs for detailed lookups. + Search for people by name, company, title, or natural language query. Returns LinkedIn profiles with rich metadata including professional headline, location, bio excerpt, follower count, and school. + + Use the returned `entity_id` with `/bio`, `/contact`, or `/education-work` to get full enrichment data. + + **Pagination:** Use `offset` and `limit` to page through results. Check `has_more` in the response to determine if more pages are available. + + **Large result sets:** Supports up to 500 results per request. For requests exceeding 100 results, the API automatically expands the search with query variations to discover more profiles, then deduplicates by LinkedIn URL. + + **Examples:** + - `?q=engineers at Anthropic in San Francisco&limit=20` + - `?q=Sam Altman&limit=1` + - `?q=senior data scientists&company=Stripe&location=New York&limit=50&offset=0` Parameters ---------- + q : str + Person name or search query + + company : typing.Optional[str] + Filter by current company + title : typing.Optional[str] Filter by job title @@ -348,32 +227,38 @@ async def search( Filter by location limit : typing.Optional[int] - Maximum results + Maximum results per page (1-500). For limits above 100, query expansion is used automatically. + + offset : typing.Optional[int] + Number of results to skip for pagination. Use with limit to page through results. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[PeopleSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/people/search", method="GET", params={ + "q": q, + "company": company, "title": title, "location": location, "limit": limit, + "offset": offset, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + PeopleSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PeopleSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -392,24 +277,29 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def bio( self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[PeopleBioResponse]: """ - Get comprehensive person profile including headline, summary, current company, location, and social profiles. This is the primary endpoint for person overview data. + Get complete person profile including bio, contact information (emails, phones, social profiles), work history (all positions with companies, titles, dates), and education (schools, degrees, fields of study). One API call returns everything. Use the entity_id from /people/search results. Parameters ---------- person_id : str + Person entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[PeopleBioResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -420,189 +310,9 @@ async def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def contact( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get contact information including email addresses, phone numbers, and social media profiles. Returns verified contact details for outreach. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/people/{jsonable_encoder(person_id)}/contact", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def education_work( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get complete professional history including work experience and education. Returns job positions with companies, titles, dates, and degree information with schools and majors. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/people/{jsonable_encoder(person_id)}/education-work", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def updates( - self, person_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to person profile data. Returns history of career moves, title changes, and profile updates with timestamps. - - Parameters - ---------- - person_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/people/{jsonable_encoder(person_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], + PeopleBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=PeopleBioResponse, # type: ignore object_=_response.json(), ), ) @@ -632,4 +342,8 @@ async def updates( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/people/types/__init__.py b/src/runcaptain/people/types/__init__.py new file mode 100644 index 0000000..26771ec --- /dev/null +++ b/src/runcaptain/people/types/__init__.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .people_bio_response import PeopleBioResponse + from .people_bio_response_current_company import PeopleBioResponseCurrentCompany + from .people_bio_response_location import PeopleBioResponseLocation + from .people_search_response import PeopleSearchResponse + from .people_search_response_results_item import PeopleSearchResponseResultsItem +_dynamic_imports: typing.Dict[str, str] = { + "PeopleBioResponse": ".people_bio_response", + "PeopleBioResponseCurrentCompany": ".people_bio_response_current_company", + "PeopleBioResponseLocation": ".people_bio_response_location", + "PeopleSearchResponse": ".people_search_response", + "PeopleSearchResponseResultsItem": ".people_search_response_results_item", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "PeopleBioResponse", + "PeopleBioResponseCurrentCompany", + "PeopleBioResponseLocation", + "PeopleSearchResponse", + "PeopleSearchResponseResultsItem", +] diff --git a/src/runcaptain/people/types/people_bio_response.py b/src/runcaptain/people/types/people_bio_response.py new file mode 100644 index 0000000..3d2a2df --- /dev/null +++ b/src/runcaptain/people/types/people_bio_response.py @@ -0,0 +1,116 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .people_bio_response_current_company import PeopleBioResponseCurrentCompany +from .people_bio_response_location import PeopleBioResponseLocation + + +class PeopleBioResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Person entity ID + """ + + full_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Full name + """ + + headline: typing.Optional[str] = pydantic.Field(default=None) + """ + Professional headline + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Professional summary + """ + + location: typing.Optional[PeopleBioResponseLocation] = None + current_company: typing.Optional[PeopleBioResponseCurrentCompany] = None + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn profile URL + """ + + profile_image_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Profile image URL + """ + + follower_count: typing.Optional[int] = pydantic.Field(default=None) + """ + LinkedIn follower count + """ + + connection_count: typing.Optional[int] = pydantic.Field(default=None) + """ + LinkedIn connection count + """ + + experience: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = pydantic.Field(default=None) + """ + Work history with companies, titles, dates, and durations + """ + + education: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = pydantic.Field(default=None) + """ + Education with schools, degrees, and fields of study + """ + + emails: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Email addresses + """ + + phone_numbers: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Phone numbers + """ + + twitter_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Twitter/X profile URL + """ + + facebook_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Facebook profile URL + """ + + github_url: typing.Optional[str] = pydantic.Field(default=None) + """ + GitHub profile URL + """ + + social_profiles: typing.Optional[typing.Dict[str, str]] = pydantic.Field(default=None) + """ + Social profiles by network name + """ + + total_positions: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of work positions + """ + + total_years_experience: typing.Optional[float] = pydantic.Field(default=None) + """ + Total years of work experience + """ + + total_degrees: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of degrees + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/people/types/people_bio_response_current_company.py b/src/runcaptain/people/types/people_bio_response_current_company.py new file mode 100644 index 0000000..232c555 --- /dev/null +++ b/src/runcaptain/people/types/people_bio_response_current_company.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PeopleBioResponseCurrentCompany(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Job title + """ + + start_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Start date + """ + + company_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/people/types/people_bio_response_location.py b/src/runcaptain/people/types/people_bio_response_location.py new file mode 100644 index 0000000..5fb6d87 --- /dev/null +++ b/src/runcaptain/people/types/people_bio_response_location.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PeopleBioResponseLocation(UniversalBaseModel): + city: typing.Optional[str] = None + state: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/people/types/people_search_response.py b/src/runcaptain/people/types/people_search_response.py new file mode 100644 index 0000000..d875925 --- /dev/null +++ b/src/runcaptain/people/types/people_search_response.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .people_search_response_results_item import PeopleSearchResponseResultsItem + + +class PeopleSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[PeopleSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum results requested + """ + + offset: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of results skipped (pagination offset) + """ + + has_more: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether more results are available beyond this page + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/people/types/people_search_response_results_item.py b/src/runcaptain/people/types/people_search_response_results_item.py new file mode 100644 index 0000000..2dfd60d --- /dev/null +++ b/src/runcaptain/people/types/people_search_response_results_item.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PeopleSearchResponseResultsItem(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + full_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Full name + """ + + headline: typing.Optional[str] = pydantic.Field(default=None) + """ + Professional headline + """ + + company: typing.Optional[str] = pydantic.Field(default=None) + """ + Current company + """ + + location: typing.Optional[str] = pydantic.Field(default=None) + """ + Location + """ + + linkedin_url: typing.Optional[str] = pydantic.Field(default=None) + """ + LinkedIn profile URL + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Professional summary or bio excerpt + """ + + followers: typing.Optional[int] = pydantic.Field(default=None) + """ + LinkedIn follower count + """ + + school: typing.Optional[str] = pydantic.Field(default=None) + """ + Most recent or notable school attended + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/query/client.py b/src/runcaptain/query/client.py index d4bf2ac..680404e 100644 --- a/src/runcaptain/query/client.py +++ b/src/runcaptain/query/client.py @@ -5,7 +5,6 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from ..types.query_response_v2 import QueryResponseV2 -from ..types.query_stream_event import QueryStreamEvent from .raw_client import AsyncRawQueryClient, RawQueryClient # this is used as the default value for optional parameters @@ -27,18 +26,22 @@ def with_raw_response(self) -> RawQueryClient: """ return self._raw_client - def collection_v2stream( + def collection_v2( self, collection_name: str, *, query: str, + idempotency_key: typing.Optional[str] = None, inference: typing.Optional[bool] = OMIT, + stream: typing.Optional[bool] = OMIT, top_k: typing.Optional[int] = OMIT, rerank: typing.Optional[bool] = OMIT, metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, custom_prompt: typing.Optional[str] = OMIT, + include_bbox: typing.Optional[bool] = OMIT, + search_results: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[QueryStreamEvent]: + ) -> QueryResponseV2: """ Execute a natural language query against a collection. @@ -77,134 +80,45 @@ def collection_v2stream( ### Notes - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. + - Text chunks are interleaved between tool events - text arrives after the agent has gathered results from a search. - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. - Parameters - ---------- - collection_name : str - - query : str - The natural language query to search for - - inference : typing.Optional[bool] - Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. - - top_k : typing.Optional[int] - Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). - - rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. - - metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] - Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or - - custom_prompt : typing.Optional[str] - Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Yields - ------ - typing.Iterator[QueryStreamEvent] - - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - response = client.query.collection_v2stream( - collection_name="collection_name", - query="query", - ) - for chunk in response: - yield chunk - """ - with self._raw_client.collection_v2stream( - collection_name, - query=query, - inference=inference, - top_k=top_k, - rerank=rerank, - metadata_filter=metadata_filter, - custom_prompt=custom_prompt, - request_options=request_options, - ) as r: - yield from r.data - - def collection_v2( - self, - collection_name: str, - *, - query: str, - inference: typing.Optional[bool] = OMIT, - top_k: typing.Optional[int] = OMIT, - rerank: typing.Optional[bool] = OMIT, - metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - custom_prompt: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> QueryResponseV2: - """ - Execute a natural language query against a collection. - - When `inference=true`, returns an AI-generated response with relevant documents. - When `inference=false`, returns raw search results with content and metadata. - - ## Streaming (SSE) - - When `stream: true` and `inference: true`, the response is a Server-Sent Events stream. Every `data:` field is a JSON object with a `type` discriminator. - - ### SSE Event Types - - | `type` value | Schema | Description | - |---|---|---| - | `text.delta` | `QueryStreamTextEvent` | Incremental text chunk of the AI response. | - | `tool.start` | `QueryStreamToolStartEvent` | The agent is performing a knowledge-base search. | - | `tool.end` | `QueryStreamToolEndEvent` | A tool call completed. `tool_call_id` correlates with the preceding `tool.start`. | - | `stream_complete` | `QueryStreamCompleteEvent` | Stream finished successfully. Close the connection. | - | `stream_error` | `QueryStreamErrorEvent` | An error occurred. Close the connection. | - - ### Example SSE Stream - - ``` - data: {"type":"tool.start","seq":1,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","args":{"query":"revenue projections Q4"}} - - data: {"type":"tool.end","seq":2,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","ok":true,"result_summary":{"resultCount":12}} + ## Bounding Box Data - data: {"type":"text.delta","seq":3,"run_id":"run_abc","data":"Based on the documents"} - data: {"type":"text.delta","seq":4,"run_id":"run_abc","data":" provided, the revenue"} - data: {"type":"text.delta","seq":5,"run_id":"run_abc","data":" projections for Q4 show"} - data: {"type":"text.delta","seq":6,"run_id":"run_abc","data":" a 15% increase over Q3."} - - data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1},"stats":{"totalTokens":150}} - ``` + Set `include_bbox: true` (inference=false only) to receive element-level layout coordinates for each search result. Each result will include a `layout` object with normalized bounding box blocks for PDF and DOCX files. - ### Notes + Each block contains: + - `type`: element type (text, title, section_header, list_item, table, figure, key_value, header, footer) + - `content`: the text content + - `page`: page number + - `bbox`: normalized 0-1 coordinates `{ top, left, width, height }` relative to page dimensions + - `confidence`: extraction confidence (high/low) when available + - `image_url`: presigned URL for figure/chart images when available - - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. - - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. + Files without OCR data (TXT, CSV, images) will have `layout: null`. Parameters ---------- collection_name : str + Name of the collection to query query : str The natural language query to search for + idempotency_key : typing.Optional[str] + UUID for request deduplication + inference : typing.Optional[bool] Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. + stream : typing.Optional[bool] + Enable real-time streaming of the response + top_k : typing.Optional[int] Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. + Enable reranking for improved relevance ordering. Uses Gemini Flash 2.5 by default, or Voyage AI rerank-2.5 as fallback. Adds ~100-300ms latency. metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or @@ -212,13 +126,19 @@ def collection_v2( custom_prompt : typing.Optional[str] Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. + include_bbox : typing.Optional[bool] + Include normalized bounding box layout data for each search result. Returns element-level positions (titles, paragraphs, tables, figures, form fields) with page coordinates for PDF and DOCX files. Only supported with inference=false. + + search_results : typing.Optional[bool] + When inference=true, include the raw search result chunks that were used as context for the LLM response. Defaults to false. Always true when inference=false. + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- QueryResponseV2 - + Successful Response - returns JSON when `stream: false`, or SSE event stream when `stream: true`. Examples -------- @@ -231,19 +151,25 @@ def collection_v2( client.query.collection_v2( collection_name="my_documents", query="What are the key terms in the contract?", - inference=False, + inference=True, + stream=True, rerank=True, top_k=10, + include_bbox=False, ) """ _response = self._raw_client.collection_v2( collection_name, query=query, + idempotency_key=idempotency_key, inference=inference, + stream=stream, top_k=top_k, rerank=rerank, metadata_filter=metadata_filter, custom_prompt=custom_prompt, + include_bbox=include_bbox, + search_results=search_results, request_options=request_options, ) return _response.data @@ -264,18 +190,22 @@ def with_raw_response(self) -> AsyncRawQueryClient: """ return self._raw_client - async def collection_v2stream( + async def collection_v2( self, collection_name: str, *, query: str, + idempotency_key: typing.Optional[str] = None, inference: typing.Optional[bool] = OMIT, + stream: typing.Optional[bool] = OMIT, top_k: typing.Optional[int] = OMIT, rerank: typing.Optional[bool] = OMIT, metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, custom_prompt: typing.Optional[str] = OMIT, + include_bbox: typing.Optional[bool] = OMIT, + search_results: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[QueryStreamEvent]: + ) -> QueryResponseV2: """ Execute a natural language query against a collection. @@ -314,143 +244,45 @@ async def collection_v2stream( ### Notes - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. + - Text chunks are interleaved between tool events - text arrives after the agent has gathered results from a search. - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. - Parameters - ---------- - collection_name : str - - query : str - The natural language query to search for - - inference : typing.Optional[bool] - Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. - - top_k : typing.Optional[int] - Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). - - rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. - - metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] - Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or - - custom_prompt : typing.Optional[str] - Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Yields - ------ - typing.AsyncIterator[QueryStreamEvent] - - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - response = await client.query.collection_v2stream( - collection_name="collection_name", - query="query", - ) - async for chunk in response: - yield chunk - - - asyncio.run(main()) - """ - async with self._raw_client.collection_v2stream( - collection_name, - query=query, - inference=inference, - top_k=top_k, - rerank=rerank, - metadata_filter=metadata_filter, - custom_prompt=custom_prompt, - request_options=request_options, - ) as r: - async for _chunk in r.data: - yield _chunk - - async def collection_v2( - self, - collection_name: str, - *, - query: str, - inference: typing.Optional[bool] = OMIT, - top_k: typing.Optional[int] = OMIT, - rerank: typing.Optional[bool] = OMIT, - metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - custom_prompt: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> QueryResponseV2: - """ - Execute a natural language query against a collection. - - When `inference=true`, returns an AI-generated response with relevant documents. - When `inference=false`, returns raw search results with content and metadata. - - ## Streaming (SSE) - - When `stream: true` and `inference: true`, the response is a Server-Sent Events stream. Every `data:` field is a JSON object with a `type` discriminator. - - ### SSE Event Types - - | `type` value | Schema | Description | - |---|---|---| - | `text.delta` | `QueryStreamTextEvent` | Incremental text chunk of the AI response. | - | `tool.start` | `QueryStreamToolStartEvent` | The agent is performing a knowledge-base search. | - | `tool.end` | `QueryStreamToolEndEvent` | A tool call completed. `tool_call_id` correlates with the preceding `tool.start`. | - | `stream_complete` | `QueryStreamCompleteEvent` | Stream finished successfully. Close the connection. | - | `stream_error` | `QueryStreamErrorEvent` | An error occurred. Close the connection. | - - ### Example SSE Stream - - ``` - data: {"type":"tool.start","seq":1,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","args":{"query":"revenue projections Q4"}} - - data: {"type":"tool.end","seq":2,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","ok":true,"result_summary":{"resultCount":12}} - - data: {"type":"text.delta","seq":3,"run_id":"run_abc","data":"Based on the documents"} - data: {"type":"text.delta","seq":4,"run_id":"run_abc","data":" provided, the revenue"} - data: {"type":"text.delta","seq":5,"run_id":"run_abc","data":" projections for Q4 show"} - data: {"type":"text.delta","seq":6,"run_id":"run_abc","data":" a 15% increase over Q3."} + ## Bounding Box Data - data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1},"stats":{"totalTokens":150}} - ``` + Set `include_bbox: true` (inference=false only) to receive element-level layout coordinates for each search result. Each result will include a `layout` object with normalized bounding box blocks for PDF and DOCX files. - ### Notes + Each block contains: + - `type`: element type (text, title, section_header, list_item, table, figure, key_value, header, footer) + - `content`: the text content + - `page`: page number + - `bbox`: normalized 0-1 coordinates `{ top, left, width, height }` relative to page dimensions + - `confidence`: extraction confidence (high/low) when available + - `image_url`: presigned URL for figure/chart images when available - - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. - - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. + Files without OCR data (TXT, CSV, images) will have `layout: null`. Parameters ---------- collection_name : str + Name of the collection to query query : str The natural language query to search for + idempotency_key : typing.Optional[str] + UUID for request deduplication + inference : typing.Optional[bool] Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. + stream : typing.Optional[bool] + Enable real-time streaming of the response + top_k : typing.Optional[int] Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. + Enable reranking for improved relevance ordering. Uses Gemini Flash 2.5 by default, or Voyage AI rerank-2.5 as fallback. Adds ~100-300ms latency. metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or @@ -458,13 +290,19 @@ async def collection_v2( custom_prompt : typing.Optional[str] Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. + include_bbox : typing.Optional[bool] + Include normalized bounding box layout data for each search result. Returns element-level positions (titles, paragraphs, tables, figures, form fields) with page coordinates for PDF and DOCX files. Only supported with inference=false. + + search_results : typing.Optional[bool] + When inference=true, include the raw search result chunks that were used as context for the LLM response. Defaults to false. Always true when inference=false. + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- QueryResponseV2 - + Successful Response - returns JSON when `stream: false`, or SSE event stream when `stream: true`. Examples -------- @@ -482,9 +320,11 @@ async def main() -> None: await client.query.collection_v2( collection_name="my_documents", query="What are the key terms in the contract?", - inference=False, + inference=True, + stream=True, rerank=True, top_k=10, + include_bbox=False, ) @@ -493,11 +333,15 @@ async def main() -> None: _response = await self._raw_client.collection_v2( collection_name, query=query, + idempotency_key=idempotency_key, inference=inference, + stream=stream, top_k=top_k, rerank=rerank, metadata_filter=metadata_filter, custom_prompt=custom_prompt, + include_bbox=include_bbox, + search_results=search_results, request_options=request_options, ) return _response.data diff --git a/src/runcaptain/query/raw_client.py b/src/runcaptain/query/raw_client.py index 22a7d87..b67309e 100644 --- a/src/runcaptain/query/raw_client.py +++ b/src/runcaptain/query/raw_client.py @@ -1,19 +1,17 @@ # This file was auto-generated by Fern from our API Definition. -import contextlib import typing from json.decoder import JSONDecodeError -from logging import error, warning from ..core.api_error import ApiError from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.http_sse._api import EventSource from ..core.jsonable_encoder import jsonable_encoder -from ..core.pydantic_utilities import parse_obj_as, parse_sse_obj +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..types.query_response_v2 import QueryResponseV2 -from ..types.query_stream_event import QueryStreamEvent +from pydantic import ValidationError # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -23,19 +21,22 @@ class RawQueryClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - @contextlib.contextmanager - def collection_v2stream( + def collection_v2( self, collection_name: str, *, query: str, + idempotency_key: typing.Optional[str] = None, inference: typing.Optional[bool] = OMIT, + stream: typing.Optional[bool] = OMIT, top_k: typing.Optional[int] = OMIT, rerank: typing.Optional[bool] = OMIT, metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, custom_prompt: typing.Optional[str] = OMIT, + include_bbox: typing.Optional[bool] = OMIT, + search_results: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Iterator[QueryStreamEvent]]]: + ) -> HttpResponse[QueryResponseV2]: """ Execute a natural language query against a collection. @@ -74,166 +75,45 @@ def collection_v2stream( ### Notes - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. + - Text chunks are interleaved between tool events - text arrives after the agent has gathered results from a search. - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. - Parameters - ---------- - collection_name : str + ## Bounding Box Data - query : str - The natural language query to search for + Set `include_bbox: true` (inference=false only) to receive element-level layout coordinates for each search result. Each result will include a `layout` object with normalized bounding box blocks for PDF and DOCX files. - inference : typing.Optional[bool] - Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. - - top_k : typing.Optional[int] - Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). + Each block contains: + - `type`: element type (text, title, section_header, list_item, table, figure, key_value, header, footer) + - `content`: the text content + - `page`: page number + - `bbox`: normalized 0-1 coordinates `{ top, left, width, height }` relative to page dimensions + - `confidence`: extraction confidence (high/low) when available + - `image_url`: presigned URL for figure/chart images when available - rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. - - metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] - Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or - - custom_prompt : typing.Optional[str] - Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Yields - ------ - typing.Iterator[HttpResponse[typing.Iterator[QueryStreamEvent]]] - - """ - with self._client_wrapper.httpx_client.stream( - f"v2/collections/{jsonable_encoder(collection_name)}/query", - method="POST", - json={ - "query": query, - "inference": inference, - "top_k": top_k, - "rerank": rerank, - "metadata_filter": metadata_filter, - "custom_prompt": custom_prompt, - "stream": True, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) as _response: - - def _stream() -> HttpResponse[typing.Iterator[QueryStreamEvent]]: - try: - if 200 <= _response.status_code < 300: - - def _iter(): - _event_source = EventSource(_response) - for _sse in _event_source.iter_sse(): - if _sse.data == None: - return - try: - yield typing.cast( - QueryStreamEvent, - parse_sse_obj( - sse=_sse, - type_=QueryStreamEvent, # type: ignore - ), - ) - except JSONDecodeError as e: - warning(f"Skipping SSE event with invalid JSON: {e}, sse: {_sse!r}") - except (TypeError, ValueError, KeyError, AttributeError) as e: - warning( - f"Skipping SSE event due to model construction error: {type(e).__name__}: {e}, sse: {_sse!r}" - ) - except Exception as e: - error( - f"Unexpected error processing SSE event: {type(e).__name__}: {e}, sse: {_sse!r}" - ) - return - - return HttpResponse(response=_response, data=_iter()) - _response.read() - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield _stream() - - def collection_v2( - self, - collection_name: str, - *, - query: str, - inference: typing.Optional[bool] = OMIT, - top_k: typing.Optional[int] = OMIT, - rerank: typing.Optional[bool] = OMIT, - metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - custom_prompt: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[QueryResponseV2]: - """ - Execute a natural language query against a collection. - - When `inference=true`, returns an AI-generated response with relevant documents. - When `inference=false`, returns raw search results with content and metadata. - - ## Streaming (SSE) - - When `stream: true` and `inference: true`, the response is a Server-Sent Events stream. Every `data:` field is a JSON object with a `type` discriminator. - - ### SSE Event Types - - | `type` value | Schema | Description | - |---|---|---| - | `text.delta` | `QueryStreamTextEvent` | Incremental text chunk of the AI response. | - | `tool.start` | `QueryStreamToolStartEvent` | The agent is performing a knowledge-base search. | - | `tool.end` | `QueryStreamToolEndEvent` | A tool call completed. `tool_call_id` correlates with the preceding `tool.start`. | - | `stream_complete` | `QueryStreamCompleteEvent` | Stream finished successfully. Close the connection. | - | `stream_error` | `QueryStreamErrorEvent` | An error occurred. Close the connection. | - - ### Example SSE Stream - - ``` - data: {"type":"tool.start","seq":1,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","args":{"query":"revenue projections Q4"}} - - data: {"type":"tool.end","seq":2,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","ok":true,"result_summary":{"resultCount":12}} - - data: {"type":"text.delta","seq":3,"run_id":"run_abc","data":"Based on the documents"} - data: {"type":"text.delta","seq":4,"run_id":"run_abc","data":" provided, the revenue"} - data: {"type":"text.delta","seq":5,"run_id":"run_abc","data":" projections for Q4 show"} - data: {"type":"text.delta","seq":6,"run_id":"run_abc","data":" a 15% increase over Q3."} - - data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1},"stats":{"totalTokens":150}} - ``` - - ### Notes - - - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. - - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. + Files without OCR data (TXT, CSV, images) will have `layout: null`. Parameters ---------- collection_name : str + Name of the collection to query query : str The natural language query to search for + idempotency_key : typing.Optional[str] + UUID for request deduplication + inference : typing.Optional[bool] Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. + stream : typing.Optional[bool] + Enable real-time streaming of the response + top_k : typing.Optional[int] Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. + Enable reranking for improved relevance ordering. Uses Gemini Flash 2.5 by default, or Voyage AI rerank-2.5 as fallback. Adds ~100-300ms latency. metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or @@ -241,13 +121,19 @@ def collection_v2( custom_prompt : typing.Optional[str] Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. + include_bbox : typing.Optional[bool] + Include normalized bounding box layout data for each search result. Returns element-level positions (titles, paragraphs, tables, figures, form fields) with page coordinates for PDF and DOCX files. Only supported with inference=false. + + search_results : typing.Optional[bool] + When inference=true, include the raw search result chunks that were used as context for the LLM response. Defaults to false. Always true when inference=false. + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- HttpResponse[QueryResponseV2] - + Successful Response - returns JSON when `stream: false`, or SSE event stream when `stream: true`. """ _response = self._client_wrapper.httpx_client.request( f"v2/collections/{jsonable_encoder(collection_name)}/query", @@ -255,14 +141,17 @@ def collection_v2( json={ "query": query, "inference": inference, + "stream": stream, "top_k": top_k, "rerank": rerank, "metadata_filter": metadata_filter, "custom_prompt": custom_prompt, - "stream": False, + "include_bbox": include_bbox, + "search_results": search_results, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -280,6 +169,10 @@ def collection_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -287,19 +180,22 @@ class AsyncRawQueryClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - @contextlib.asynccontextmanager - async def collection_v2stream( + async def collection_v2( self, collection_name: str, *, query: str, + idempotency_key: typing.Optional[str] = None, inference: typing.Optional[bool] = OMIT, + stream: typing.Optional[bool] = OMIT, top_k: typing.Optional[int] = OMIT, rerank: typing.Optional[bool] = OMIT, metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, custom_prompt: typing.Optional[str] = OMIT, + include_bbox: typing.Optional[bool] = OMIT, + search_results: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[QueryStreamEvent]]]: + ) -> AsyncHttpResponse[QueryResponseV2]: """ Execute a natural language query against a collection. @@ -338,166 +234,45 @@ async def collection_v2stream( ### Notes - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. + - Text chunks are interleaved between tool events - text arrives after the agent has gathered results from a search. - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. - Parameters - ---------- - collection_name : str + ## Bounding Box Data - query : str - The natural language query to search for + Set `include_bbox: true` (inference=false only) to receive element-level layout coordinates for each search result. Each result will include a `layout` object with normalized bounding box blocks for PDF and DOCX files. - inference : typing.Optional[bool] - Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. - - top_k : typing.Optional[int] - Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). + Each block contains: + - `type`: element type (text, title, section_header, list_item, table, figure, key_value, header, footer) + - `content`: the text content + - `page`: page number + - `bbox`: normalized 0-1 coordinates `{ top, left, width, height }` relative to page dimensions + - `confidence`: extraction confidence (high/low) when available + - `image_url`: presigned URL for figure/chart images when available - rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. - - metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] - Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or - - custom_prompt : typing.Optional[str] - Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Yields - ------ - typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[QueryStreamEvent]]] - - """ - async with self._client_wrapper.httpx_client.stream( - f"v2/collections/{jsonable_encoder(collection_name)}/query", - method="POST", - json={ - "query": query, - "inference": inference, - "top_k": top_k, - "rerank": rerank, - "metadata_filter": metadata_filter, - "custom_prompt": custom_prompt, - "stream": True, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) as _response: - - async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[QueryStreamEvent]]: - try: - if 200 <= _response.status_code < 300: - - async def _iter(): - _event_source = EventSource(_response) - async for _sse in _event_source.aiter_sse(): - if _sse.data == None: - return - try: - yield typing.cast( - QueryStreamEvent, - parse_sse_obj( - sse=_sse, - type_=QueryStreamEvent, # type: ignore - ), - ) - except JSONDecodeError as e: - warning(f"Skipping SSE event with invalid JSON: {e}, sse: {_sse!r}") - except (TypeError, ValueError, KeyError, AttributeError) as e: - warning( - f"Skipping SSE event due to model construction error: {type(e).__name__}: {e}, sse: {_sse!r}" - ) - except Exception as e: - error( - f"Unexpected error processing SSE event: {type(e).__name__}: {e}, sse: {_sse!r}" - ) - return - - return AsyncHttpResponse(response=_response, data=_iter()) - await _response.aread() - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield await _stream() - - async def collection_v2( - self, - collection_name: str, - *, - query: str, - inference: typing.Optional[bool] = OMIT, - top_k: typing.Optional[int] = OMIT, - rerank: typing.Optional[bool] = OMIT, - metadata_filter: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - custom_prompt: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[QueryResponseV2]: - """ - Execute a natural language query against a collection. - - When `inference=true`, returns an AI-generated response with relevant documents. - When `inference=false`, returns raw search results with content and metadata. - - ## Streaming (SSE) - - When `stream: true` and `inference: true`, the response is a Server-Sent Events stream. Every `data:` field is a JSON object with a `type` discriminator. - - ### SSE Event Types - - | `type` value | Schema | Description | - |---|---|---| - | `text.delta` | `QueryStreamTextEvent` | Incremental text chunk of the AI response. | - | `tool.start` | `QueryStreamToolStartEvent` | The agent is performing a knowledge-base search. | - | `tool.end` | `QueryStreamToolEndEvent` | A tool call completed. `tool_call_id` correlates with the preceding `tool.start`. | - | `stream_complete` | `QueryStreamCompleteEvent` | Stream finished successfully. Close the connection. | - | `stream_error` | `QueryStreamErrorEvent` | An error occurred. Close the connection. | - - ### Example SSE Stream - - ``` - data: {"type":"tool.start","seq":1,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","args":{"query":"revenue projections Q4"}} - - data: {"type":"tool.end","seq":2,"run_id":"run_abc","tool_call_id":"tc_1","name":"searchKnowledgeBase","ok":true,"result_summary":{"resultCount":12}} - - data: {"type":"text.delta","seq":3,"run_id":"run_abc","data":"Based on the documents"} - data: {"type":"text.delta","seq":4,"run_id":"run_abc","data":" provided, the revenue"} - data: {"type":"text.delta","seq":5,"run_id":"run_abc","data":" projections for Q4 show"} - data: {"type":"text.delta","seq":6,"run_id":"run_abc","data":" a 15% increase over Q3."} - - data: {"type":"stream_complete","metadata":{"totalResults":12,"totalSearches":1},"stats":{"totalTokens":150}} - ``` - - ### Notes - - - The agent may perform multiple searches per query. Each search produces a `tool.start` / `tool.end` pair. - - Text chunks are interleaved between tool events — text arrives after the agent has gathered results from a search. - - Connect with `Accept: text/event-stream` and set a generous timeout (120s+) for long responses. + Files without OCR data (TXT, CSV, images) will have `layout: null`. Parameters ---------- collection_name : str + Name of the collection to query query : str The natural language query to search for + idempotency_key : typing.Optional[str] + UUID for request deduplication + inference : typing.Optional[bool] Enable LLM-generated answers based on the relevant sections retrieved. When false, returns raw search results. + stream : typing.Optional[bool] + Enable real-time streaming of the response + top_k : typing.Optional[int] Number of results to return. Only valid when inference=false. Not supported when inference=true (the agent controls its own search strategy). rerank : typing.Optional[bool] - Enable Voyage AI rerank-2.5 reranking for improved relevance ordering. Adds ~100-300ms latency. + Enable reranking for improved relevance ordering. Uses Gemini Flash 2.5 by default, or Voyage AI rerank-2.5 as fallback. Adds ~100-300ms latency. metadata_filter : typing.Optional[typing.Dict[str, typing.Any]] Filter expression for vector search. Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or @@ -505,13 +280,19 @@ async def collection_v2( custom_prompt : typing.Optional[str] Custom system prompt to override the default RAG prompt when inference=true. Allows customizing how the LLM processes and responds to the query with the retrieved context. + include_bbox : typing.Optional[bool] + Include normalized bounding box layout data for each search result. Returns element-level positions (titles, paragraphs, tables, figures, form fields) with page coordinates for PDF and DOCX files. Only supported with inference=false. + + search_results : typing.Optional[bool] + When inference=true, include the raw search result chunks that were used as context for the LLM response. Defaults to false. Always true when inference=false. + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- AsyncHttpResponse[QueryResponseV2] - + Successful Response - returns JSON when `stream: false`, or SSE event stream when `stream: true`. """ _response = await self._client_wrapper.httpx_client.request( f"v2/collections/{jsonable_encoder(collection_name)}/query", @@ -519,14 +300,17 @@ async def collection_v2( json={ "query": query, "inference": inference, + "stream": stream, "top_k": top_k, "rerank": rerank, "metadata_filter": metadata_filter, "custom_prompt": custom_prompt, - "stream": False, + "include_bbox": include_bbox, + "search_results": search_results, }, headers={ "content-type": "application/json", + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, }, request_options=request_options, omit=OMIT, @@ -544,4 +328,8 @@ async def collection_v2( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/sandbox_data/__init__.py b/src/runcaptain/sandbox_data/__init__.py new file mode 100644 index 0000000..91a011d --- /dev/null +++ b/src/runcaptain/sandbox_data/__init__.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + FundamentalsLookupTableValuesResponse, + FundamentalsLookupTableValuesResponseValuesItem, + FundamentalsLookupTablesResponse, + FundamentalsLookupTablesResponseTablesItem, + FundamentalsSandboxResponse, + FundamentalsSandboxResponseCompaniesItem, + FundamentalsSandboxResponseInvestorsItem, + FundamentalsSandboxResponsePeopleItem, + ) +_dynamic_imports: typing.Dict[str, str] = { + "FundamentalsLookupTableValuesResponse": ".types", + "FundamentalsLookupTableValuesResponseValuesItem": ".types", + "FundamentalsLookupTablesResponse": ".types", + "FundamentalsLookupTablesResponseTablesItem": ".types", + "FundamentalsSandboxResponse": ".types", + "FundamentalsSandboxResponseCompaniesItem": ".types", + "FundamentalsSandboxResponseInvestorsItem": ".types", + "FundamentalsSandboxResponsePeopleItem": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "FundamentalsLookupTableValuesResponse", + "FundamentalsLookupTableValuesResponseValuesItem", + "FundamentalsLookupTablesResponse", + "FundamentalsLookupTablesResponseTablesItem", + "FundamentalsSandboxResponse", + "FundamentalsSandboxResponseCompaniesItem", + "FundamentalsSandboxResponseInvestorsItem", + "FundamentalsSandboxResponsePeopleItem", +] diff --git a/src/runcaptain/fundamentals/client.py b/src/runcaptain/sandbox_data/client.py similarity index 64% rename from src/runcaptain/fundamentals/client.py rename to src/runcaptain/sandbox_data/client.py index c17c165..e1e6d23 100644 --- a/src/runcaptain/fundamentals/client.py +++ b/src/runcaptain/sandbox_data/client.py @@ -4,25 +4,30 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions -from .raw_client import AsyncRawFundamentalsClient, RawFundamentalsClient +from .raw_client import AsyncRawSandboxDataClient, RawSandboxDataClient +from .types.fundamentals_lookup_table_values_response import FundamentalsLookupTableValuesResponse +from .types.fundamentals_lookup_tables_response import FundamentalsLookupTablesResponse +from .types.fundamentals_sandbox_response import FundamentalsSandboxResponse -class FundamentalsClient: +class SandboxDataClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawFundamentalsClient(client_wrapper=client_wrapper) + self._raw_client = RawSandboxDataClient(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawFundamentalsClient: + def with_raw_response(self) -> RawSandboxDataClient: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawFundamentalsClient + RawSandboxDataClient """ return self._raw_client - def sandbox(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def fundamentals_sandbox( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> FundamentalsSandboxResponse: """ Get sample entities for testing and development. Returns mock company, person, investor, and fund data for sandbox environment testing. @@ -33,7 +38,7 @@ def sandbox(self, *, request_options: typing.Optional[RequestOptions] = None) -> Returns ------- - typing.Dict[str, typing.Any] + FundamentalsSandboxResponse Successful response Examples @@ -44,12 +49,14 @@ def sandbox(self, *, request_options: typing.Optional[RequestOptions] = None) -> organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.fundamentals.sandbox() + client.sandbox_data.fundamentals_sandbox() """ - _response = self._raw_client.sandbox(request_options=request_options) + _response = self._raw_client.fundamentals_sandbox(request_options=request_options) return _response.data - def lookup_tables(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def fundamentals_lookup_tables( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> FundamentalsLookupTablesResponse: """ Get available reference lookup tables including industry codes, country codes, and entity type classifications. Returns list of available tables with descriptions. @@ -60,7 +67,7 @@ def lookup_tables(self, *, request_options: typing.Optional[RequestOptions] = No Returns ------- - typing.Dict[str, typing.Any] + FundamentalsLookupTablesResponse Successful response Examples @@ -71,27 +78,28 @@ def lookup_tables(self, *, request_options: typing.Optional[RequestOptions] = No organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.fundamentals.lookup_tables() + client.sandbox_data.fundamentals_lookup_tables() """ - _response = self._raw_client.lookup_tables(request_options=request_options) + _response = self._raw_client.fundamentals_lookup_tables(request_options=request_options) return _response.data - def lookup_table_values( + def fundamentals_lookup_table_values( self, table_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> FundamentalsLookupTableValuesResponse: """ Get values from a specific lookup table. Returns table data with codes, descriptions, and hierarchical relationships for reference data. Parameters ---------- table_id : str + Lookup table ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundamentalsLookupTableValuesResponse Successful response Examples @@ -102,30 +110,32 @@ def lookup_table_values( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.fundamentals.lookup_table_values( + client.sandbox_data.fundamentals_lookup_table_values( table_id="sectors", ) """ - _response = self._raw_client.lookup_table_values(table_id, request_options=request_options) + _response = self._raw_client.fundamentals_lookup_table_values(table_id, request_options=request_options) return _response.data -class AsyncFundamentalsClient: +class AsyncSandboxDataClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawFundamentalsClient(client_wrapper=client_wrapper) + self._raw_client = AsyncRawSandboxDataClient(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawFundamentalsClient: + def with_raw_response(self) -> AsyncRawSandboxDataClient: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawFundamentalsClient + AsyncRawSandboxDataClient """ return self._raw_client - async def sandbox(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + async def fundamentals_sandbox( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> FundamentalsSandboxResponse: """ Get sample entities for testing and development. Returns mock company, person, investor, and fund data for sandbox environment testing. @@ -136,7 +146,7 @@ async def sandbox(self, *, request_options: typing.Optional[RequestOptions] = No Returns ------- - typing.Dict[str, typing.Any] + FundamentalsSandboxResponse Successful response Examples @@ -152,17 +162,17 @@ async def sandbox(self, *, request_options: typing.Optional[RequestOptions] = No async def main() -> None: - await client.fundamentals.sandbox() + await client.sandbox_data.fundamentals_sandbox() asyncio.run(main()) """ - _response = await self._raw_client.sandbox(request_options=request_options) + _response = await self._raw_client.fundamentals_sandbox(request_options=request_options) return _response.data - async def lookup_tables( + async def fundamentals_lookup_tables( self, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> FundamentalsLookupTablesResponse: """ Get available reference lookup tables including industry codes, country codes, and entity type classifications. Returns list of available tables with descriptions. @@ -173,7 +183,7 @@ async def lookup_tables( Returns ------- - typing.Dict[str, typing.Any] + FundamentalsLookupTablesResponse Successful response Examples @@ -189,30 +199,31 @@ async def lookup_tables( async def main() -> None: - await client.fundamentals.lookup_tables() + await client.sandbox_data.fundamentals_lookup_tables() asyncio.run(main()) """ - _response = await self._raw_client.lookup_tables(request_options=request_options) + _response = await self._raw_client.fundamentals_lookup_tables(request_options=request_options) return _response.data - async def lookup_table_values( + async def fundamentals_lookup_table_values( self, table_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> FundamentalsLookupTableValuesResponse: """ Get values from a specific lookup table. Returns table data with codes, descriptions, and hierarchical relationships for reference data. Parameters ---------- table_id : str + Lookup table ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + FundamentalsLookupTableValuesResponse Successful response Examples @@ -228,12 +239,12 @@ async def lookup_table_values( async def main() -> None: - await client.fundamentals.lookup_table_values( + await client.sandbox_data.fundamentals_lookup_table_values( table_id="sectors", ) asyncio.run(main()) """ - _response = await self._raw_client.lookup_table_values(table_id, request_options=request_options) + _response = await self._raw_client.fundamentals_lookup_table_values(table_id, request_options=request_options) return _response.data diff --git a/src/runcaptain/fundamentals/raw_client.py b/src/runcaptain/sandbox_data/raw_client.py similarity index 77% rename from src/runcaptain/fundamentals/raw_client.py rename to src/runcaptain/sandbox_data/raw_client.py index 10ce990..f72a86b 100644 --- a/src/runcaptain/fundamentals/raw_client.py +++ b/src/runcaptain/sandbox_data/raw_client.py @@ -7,19 +7,24 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.unauthorized_error import UnauthorizedError +from .types.fundamentals_lookup_table_values_response import FundamentalsLookupTableValuesResponse +from .types.fundamentals_lookup_tables_response import FundamentalsLookupTablesResponse +from .types.fundamentals_sandbox_response import FundamentalsSandboxResponse +from pydantic import ValidationError -class RawFundamentalsClient: +class RawSandboxDataClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def sandbox( + def fundamentals_sandbox( self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[FundamentalsSandboxResponse]: """ Get sample entities for testing and development. Returns mock company, person, investor, and fund data for sandbox environment testing. @@ -30,7 +35,7 @@ def sandbox( Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[FundamentalsSandboxResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -41,9 +46,9 @@ def sandbox( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundamentalsSandboxResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundamentalsSandboxResponse, # type: ignore object_=_response.json(), ), ) @@ -62,11 +67,15 @@ def sandbox( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def lookup_tables( + def fundamentals_lookup_tables( self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[FundamentalsLookupTablesResponse]: """ Get available reference lookup tables including industry codes, country codes, and entity type classifications. Returns list of available tables with descriptions. @@ -77,7 +86,7 @@ def lookup_tables( Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[FundamentalsLookupTablesResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -88,9 +97,9 @@ def lookup_tables( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundamentalsLookupTablesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundamentalsLookupTablesResponse, # type: ignore object_=_response.json(), ), ) @@ -109,24 +118,29 @@ def lookup_tables( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def lookup_table_values( + def fundamentals_lookup_table_values( self, table_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[FundamentalsLookupTableValuesResponse]: """ Get values from a specific lookup table. Returns table data with codes, descriptions, and hierarchical relationships for reference data. Parameters ---------- table_id : str + Lookup table ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[FundamentalsLookupTableValuesResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -137,9 +151,9 @@ def lookup_table_values( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundamentalsLookupTableValuesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundamentalsLookupTableValuesResponse, # type: ignore object_=_response.json(), ), ) @@ -169,16 +183,20 @@ def lookup_table_values( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) -class AsyncRawFundamentalsClient: +class AsyncRawSandboxDataClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - async def sandbox( + async def fundamentals_sandbox( self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[FundamentalsSandboxResponse]: """ Get sample entities for testing and development. Returns mock company, person, investor, and fund data for sandbox environment testing. @@ -189,7 +207,7 @@ async def sandbox( Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[FundamentalsSandboxResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -200,9 +218,9 @@ async def sandbox( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundamentalsSandboxResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundamentalsSandboxResponse, # type: ignore object_=_response.json(), ), ) @@ -221,11 +239,15 @@ async def sandbox( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def lookup_tables( + async def fundamentals_lookup_tables( self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[FundamentalsLookupTablesResponse]: """ Get available reference lookup tables including industry codes, country codes, and entity type classifications. Returns list of available tables with descriptions. @@ -236,7 +258,7 @@ async def lookup_tables( Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[FundamentalsLookupTablesResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -247,9 +269,9 @@ async def lookup_tables( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundamentalsLookupTablesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundamentalsLookupTablesResponse, # type: ignore object_=_response.json(), ), ) @@ -268,24 +290,29 @@ async def lookup_tables( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def lookup_table_values( + async def fundamentals_lookup_table_values( self, table_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[FundamentalsLookupTableValuesResponse]: """ Get values from a specific lookup table. Returns table data with codes, descriptions, and hierarchical relationships for reference data. Parameters ---------- table_id : str + Lookup table ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[FundamentalsLookupTableValuesResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -296,9 +323,9 @@ async def lookup_table_values( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + FundamentalsLookupTableValuesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=FundamentalsLookupTableValuesResponse, # type: ignore object_=_response.json(), ), ) @@ -328,4 +355,8 @@ async def lookup_table_values( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/sandbox_data/types/__init__.py b/src/runcaptain/sandbox_data/types/__init__.py new file mode 100644 index 0000000..f3dcf78 --- /dev/null +++ b/src/runcaptain/sandbox_data/types/__init__.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .fundamentals_lookup_table_values_response import FundamentalsLookupTableValuesResponse + from .fundamentals_lookup_table_values_response_values_item import FundamentalsLookupTableValuesResponseValuesItem + from .fundamentals_lookup_tables_response import FundamentalsLookupTablesResponse + from .fundamentals_lookup_tables_response_tables_item import FundamentalsLookupTablesResponseTablesItem + from .fundamentals_sandbox_response import FundamentalsSandboxResponse + from .fundamentals_sandbox_response_companies_item import FundamentalsSandboxResponseCompaniesItem + from .fundamentals_sandbox_response_investors_item import FundamentalsSandboxResponseInvestorsItem + from .fundamentals_sandbox_response_people_item import FundamentalsSandboxResponsePeopleItem +_dynamic_imports: typing.Dict[str, str] = { + "FundamentalsLookupTableValuesResponse": ".fundamentals_lookup_table_values_response", + "FundamentalsLookupTableValuesResponseValuesItem": ".fundamentals_lookup_table_values_response_values_item", + "FundamentalsLookupTablesResponse": ".fundamentals_lookup_tables_response", + "FundamentalsLookupTablesResponseTablesItem": ".fundamentals_lookup_tables_response_tables_item", + "FundamentalsSandboxResponse": ".fundamentals_sandbox_response", + "FundamentalsSandboxResponseCompaniesItem": ".fundamentals_sandbox_response_companies_item", + "FundamentalsSandboxResponseInvestorsItem": ".fundamentals_sandbox_response_investors_item", + "FundamentalsSandboxResponsePeopleItem": ".fundamentals_sandbox_response_people_item", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "FundamentalsLookupTableValuesResponse", + "FundamentalsLookupTableValuesResponseValuesItem", + "FundamentalsLookupTablesResponse", + "FundamentalsLookupTablesResponseTablesItem", + "FundamentalsSandboxResponse", + "FundamentalsSandboxResponseCompaniesItem", + "FundamentalsSandboxResponseInvestorsItem", + "FundamentalsSandboxResponsePeopleItem", +] diff --git a/src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response.py b/src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response.py new file mode 100644 index 0000000..d273853 --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .fundamentals_lookup_table_values_response_values_item import FundamentalsLookupTableValuesResponseValuesItem + + +class FundamentalsLookupTableValuesResponse(UniversalBaseModel): + table_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Lookup table identifier + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Table name + """ + + values: typing.Optional[typing.List[FundamentalsLookupTableValuesResponseValuesItem]] = pydantic.Field(default=None) + """ + Table values + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response_values_item.py b/src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response_values_item.py new file mode 100644 index 0000000..70c2dc7 --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_lookup_table_values_response_values_item.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundamentalsLookupTableValuesResponseValuesItem(UniversalBaseModel): + code: typing.Optional[str] = pydantic.Field(default=None) + """ + Value code + """ + + label: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable label + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response.py b/src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response.py new file mode 100644 index 0000000..da00fd5 --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .fundamentals_lookup_tables_response_tables_item import FundamentalsLookupTablesResponseTablesItem + + +class FundamentalsLookupTablesResponse(UniversalBaseModel): + tables: typing.Optional[typing.List[FundamentalsLookupTablesResponseTablesItem]] = pydantic.Field(default=None) + """ + Available lookup tables + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response_tables_item.py b/src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response_tables_item.py new file mode 100644 index 0000000..92e72cc --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_lookup_tables_response_tables_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundamentalsLookupTablesResponseTablesItem(UniversalBaseModel): + table_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Lookup table identifier + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Table name + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + What this table contains + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response.py b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response.py new file mode 100644 index 0000000..0b40017 --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .fundamentals_sandbox_response_companies_item import FundamentalsSandboxResponseCompaniesItem +from .fundamentals_sandbox_response_investors_item import FundamentalsSandboxResponseInvestorsItem +from .fundamentals_sandbox_response_people_item import FundamentalsSandboxResponsePeopleItem + + +class FundamentalsSandboxResponse(UniversalBaseModel): + companies: typing.Optional[typing.List[FundamentalsSandboxResponseCompaniesItem]] = pydantic.Field(default=None) + """ + Pre-loaded sample company entities + """ + + investors: typing.Optional[typing.List[FundamentalsSandboxResponseInvestorsItem]] = pydantic.Field(default=None) + """ + Pre-loaded sample investor entities + """ + + people: typing.Optional[typing.List[FundamentalsSandboxResponsePeopleItem]] = pydantic.Field(default=None) + """ + Pre-loaded sample person entities + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_companies_item.py b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_companies_item.py new file mode 100644 index 0000000..5c4d193 --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_companies_item.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundamentalsSandboxResponseCompaniesItem(UniversalBaseModel): + entity_id: typing.Optional[str] = None + name: typing.Optional[str] = None + website: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_investors_item.py b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_investors_item.py new file mode 100644 index 0000000..7efc05c --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_investors_item.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundamentalsSandboxResponseInvestorsItem(UniversalBaseModel): + entity_id: typing.Optional[str] = None + name: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_people_item.py b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_people_item.py new file mode 100644 index 0000000..f779fab --- /dev/null +++ b/src/runcaptain/sandbox_data/types/fundamentals_sandbox_response_people_item.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FundamentalsSandboxResponsePeopleItem(UniversalBaseModel): + entity_id: typing.Optional[str] = None + name: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/__init__.py b/src/runcaptain/service_providers/__init__.py index 5cde020..33311f4 100644 --- a/src/runcaptain/service_providers/__init__.py +++ b/src/runcaptain/service_providers/__init__.py @@ -2,3 +2,72 @@ # isort: skip_file +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + ServiceProvidersBioResponse, + ServiceProvidersCompaniesResponse, + ServiceProvidersCompaniesResponseCompaniesItem, + ServiceProvidersDealsResponse, + ServiceProvidersDealsResponseDealsItem, + ServiceProvidersFundsResponse, + ServiceProvidersFundsResponseFundsItem, + ServiceProvidersInvestorsResponse, + ServiceProvidersInvestorsResponseInvestorsItem, + ServiceProvidersSearchResponse, + ServiceProvidersSearchResponseResultsItem, + ServiceProvidersSearchResponseResultsItemLocation, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ServiceProvidersBioResponse": ".types", + "ServiceProvidersCompaniesResponse": ".types", + "ServiceProvidersCompaniesResponseCompaniesItem": ".types", + "ServiceProvidersDealsResponse": ".types", + "ServiceProvidersDealsResponseDealsItem": ".types", + "ServiceProvidersFundsResponse": ".types", + "ServiceProvidersFundsResponseFundsItem": ".types", + "ServiceProvidersInvestorsResponse": ".types", + "ServiceProvidersInvestorsResponseInvestorsItem": ".types", + "ServiceProvidersSearchResponse": ".types", + "ServiceProvidersSearchResponseResultsItem": ".types", + "ServiceProvidersSearchResponseResultsItemLocation": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ServiceProvidersBioResponse", + "ServiceProvidersCompaniesResponse", + "ServiceProvidersCompaniesResponseCompaniesItem", + "ServiceProvidersDealsResponse", + "ServiceProvidersDealsResponseDealsItem", + "ServiceProvidersFundsResponse", + "ServiceProvidersFundsResponseFundsItem", + "ServiceProvidersInvestorsResponse", + "ServiceProvidersInvestorsResponseInvestorsItem", + "ServiceProvidersSearchResponse", + "ServiceProvidersSearchResponseResultsItem", + "ServiceProvidersSearchResponseResultsItemLocation", +] diff --git a/src/runcaptain/service_providers/client.py b/src/runcaptain/service_providers/client.py index 1801466..a792956 100644 --- a/src/runcaptain/service_providers/client.py +++ b/src/runcaptain/service_providers/client.py @@ -5,6 +5,12 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawServiceProvidersClient, RawServiceProvidersClient +from .types.service_providers_bio_response import ServiceProvidersBioResponse +from .types.service_providers_companies_response import ServiceProvidersCompaniesResponse +from .types.service_providers_deals_response import ServiceProvidersDealsResponse +from .types.service_providers_funds_response import ServiceProvidersFundsResponse +from .types.service_providers_investors_response import ServiceProvidersInvestorsResponse +from .types.service_providers_search_response import ServiceProvidersSearchResponse class ServiceProvidersClient: @@ -25,27 +31,31 @@ def with_raw_response(self) -> RawServiceProvidersClient: def search( self, *, + q: str, limit: typing.Optional[int] = None, provider_type: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersSearchResponse: """ Search for service providers including law firms, accounting firms, investment banks, and consultants. Returns matching provider profiles with specializations and notable clients. Parameters ---------- + q : str + Provider name or keyword (e.g., 'Wilson Sonsini') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) provider_type : typing.Optional[str] - Provider type filter + Filter by provider type (e.g., 'law', 'accounting', 'investment_bank', 'consulting') request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersSearchResponse Successful response Examples @@ -56,27 +66,33 @@ def search( organization_id="YOUR_ORGANIZATION_ID", key="YOUR_KEY", ) - client.service_providers.search() + client.service_providers.search( + q="Wilson Sonsini", + limit=10, + ) """ - _response = self._raw_client.search(limit=limit, provider_type=provider_type, request_options=request_options) + _response = self._raw_client.search( + q=q, limit=limit, provider_type=provider_type, request_options=request_options + ) return _response.data def bio( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersBioResponse: """ Get comprehensive service provider profile including firm description, practice areas, office locations, and notable work. This is the primary endpoint for provider overview data. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersBioResponse Successful response Examples @@ -96,20 +112,21 @@ def bio( def companies( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersCompaniesResponse: """ Get companies that have engaged this service provider. Returns client list with engagement types and sectors served. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersCompaniesResponse Successful response Examples @@ -129,20 +146,21 @@ def companies( def deals( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersDealsResponse: """ Get deals where this provider was involved as advisor, counsel, or banker. Returns transaction history with roles and deal values. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersDealsResponse Successful response Examples @@ -162,20 +180,21 @@ def deals( def investors( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersInvestorsResponse: """ Get investors that have engaged this service provider. Returns investor clients with engagement types and fund formation work. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersInvestorsResponse Successful response Examples @@ -195,20 +214,21 @@ def investors( def funds( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersFundsResponse: """ Get funds that have engaged this service provider. Returns fund formation and compliance work with engagement details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersFundsResponse Successful response Examples @@ -226,23 +246,21 @@ def funds( _response = self._raw_client.funds(provider_id, request_options=request_options) return _response.data - def limited_partners( - self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + def limited_partners(self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. + **Coming Soon** - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -259,39 +277,6 @@ def limited_partners( _response = self._raw_client.limited_partners(provider_id, request_options=request_options) return _response.data - def updates( - self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to service provider profile data. Returns history of changes including new engagements, office openings, and team changes with timestamps. - - Parameters - ---------- - provider_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - from runcaptain import Captain - - client = Captain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - client.service_providers.updates( - provider_id="wilson-sonsini", - ) - """ - _response = self._raw_client.updates(provider_id, request_options=request_options) - return _response.data - class AsyncServiceProvidersClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -311,27 +296,31 @@ def with_raw_response(self) -> AsyncRawServiceProvidersClient: async def search( self, *, + q: str, limit: typing.Optional[int] = None, provider_type: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersSearchResponse: """ Search for service providers including law firms, accounting firms, investment banks, and consultants. Returns matching provider profiles with specializations and notable clients. Parameters ---------- + q : str + Provider name or keyword (e.g., 'Wilson Sonsini') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) provider_type : typing.Optional[str] - Provider type filter + Filter by provider type (e.g., 'law', 'accounting', 'investment_bank', 'consulting') request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersSearchResponse Successful response Examples @@ -347,32 +336,36 @@ async def search( async def main() -> None: - await client.service_providers.search() + await client.service_providers.search( + q="Wilson Sonsini", + limit=10, + ) asyncio.run(main()) """ _response = await self._raw_client.search( - limit=limit, provider_type=provider_type, request_options=request_options + q=q, limit=limit, provider_type=provider_type, request_options=request_options ) return _response.data async def bio( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersBioResponse: """ Get comprehensive service provider profile including firm description, practice areas, office locations, and notable work. This is the primary endpoint for provider overview data. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersBioResponse Successful response Examples @@ -400,20 +393,21 @@ async def main() -> None: async def companies( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersCompaniesResponse: """ Get companies that have engaged this service provider. Returns client list with engagement types and sectors served. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersCompaniesResponse Successful response Examples @@ -441,20 +435,21 @@ async def main() -> None: async def deals( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersDealsResponse: """ Get deals where this provider was involved as advisor, counsel, or banker. Returns transaction history with roles and deal values. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersDealsResponse Successful response Examples @@ -482,20 +477,21 @@ async def main() -> None: async def investors( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersInvestorsResponse: """ Get investors that have engaged this service provider. Returns investor clients with engagement types and fund formation work. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersInvestorsResponse Successful response Examples @@ -523,20 +519,21 @@ async def main() -> None: async def funds( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> ServiceProvidersFundsResponse: """ Get funds that have engaged this service provider. Returns fund formation and compliance work with engagement details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] + ServiceProvidersFundsResponse Successful response Examples @@ -564,21 +561,21 @@ async def main() -> None: async def limited_partners( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: + ) -> None: """ - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. + **Coming Soon** - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - typing.Dict[str, typing.Any] - Successful response + None Examples -------- @@ -602,44 +599,3 @@ async def main() -> None: """ _response = await self._raw_client.limited_partners(provider_id, request_options=request_options) return _response.data - - async def updates( - self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Any]: - """ - Get changelog of updates to service provider profile data. Returns history of changes including new engagements, office openings, and team changes with timestamps. - - Parameters - ---------- - provider_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Any] - Successful response - - Examples - -------- - import asyncio - - from runcaptain import AsyncCaptain - - client = AsyncCaptain( - organization_id="YOUR_ORGANIZATION_ID", - key="YOUR_KEY", - ) - - - async def main() -> None: - await client.service_providers.updates( - provider_id="wilson-sonsini", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.updates(provider_id, request_options=request_options) - return _response.data diff --git a/src/runcaptain/service_providers/raw_client.py b/src/runcaptain/service_providers/raw_client.py index 8d5f208..521c96c 100644 --- a/src/runcaptain/service_providers/raw_client.py +++ b/src/runcaptain/service_providers/raw_client.py @@ -7,11 +7,19 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder +from ..core.parse_error import ParsingError from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.not_found_error import NotFoundError from ..errors.not_implemented_error import NotImplementedError from ..errors.unauthorized_error import UnauthorizedError +from .types.service_providers_bio_response import ServiceProvidersBioResponse +from .types.service_providers_companies_response import ServiceProvidersCompaniesResponse +from .types.service_providers_deals_response import ServiceProvidersDealsResponse +from .types.service_providers_funds_response import ServiceProvidersFundsResponse +from .types.service_providers_investors_response import ServiceProvidersInvestorsResponse +from .types.service_providers_search_response import ServiceProvidersSearchResponse +from pydantic import ValidationError class RawServiceProvidersClient: @@ -21,33 +29,38 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def search( self, *, + q: str, limit: typing.Optional[int] = None, provider_type: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[ServiceProvidersSearchResponse]: """ Search for service providers including law firms, accounting firms, investment banks, and consultants. Returns matching provider profiles with specializations and notable clients. Parameters ---------- + q : str + Provider name or keyword (e.g., 'Wilson Sonsini') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) provider_type : typing.Optional[str] - Provider type filter + Filter by provider type (e.g., 'law', 'accounting', 'investment_bank', 'consulting') request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[ServiceProvidersSearchResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/service-providers/search", method="GET", params={ + "q": q, "limit": limit, "provider_type": provider_type, }, @@ -56,9 +69,9 @@ def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -77,24 +90,29 @@ def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def bio( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[ServiceProvidersBioResponse]: """ Get comprehensive service provider profile including firm description, practice areas, office locations, and notable work. This is the primary endpoint for provider overview data. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[ServiceProvidersBioResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -105,9 +123,9 @@ def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersBioResponse, # type: ignore object_=_response.json(), ), ) @@ -137,24 +155,29 @@ def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def companies( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[ServiceProvidersCompaniesResponse]: """ Get companies that have engaged this service provider. Returns client list with engagement types and sectors served. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[ServiceProvidersCompaniesResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -165,9 +188,9 @@ def companies( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersCompaniesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersCompaniesResponse, # type: ignore object_=_response.json(), ), ) @@ -208,24 +231,29 @@ def companies( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def deals( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[ServiceProvidersDealsResponse]: """ Get deals where this provider was involved as advisor, counsel, or banker. Returns transaction history with roles and deal values. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[ServiceProvidersDealsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -236,9 +264,9 @@ def deals( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersDealsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersDealsResponse, # type: ignore object_=_response.json(), ), ) @@ -279,24 +307,29 @@ def deals( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def investors( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[ServiceProvidersInvestorsResponse]: """ Get investors that have engaged this service provider. Returns investor clients with engagement types and fund formation work. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[ServiceProvidersInvestorsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -307,9 +340,9 @@ def investors( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersInvestorsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersInvestorsResponse, # type: ignore object_=_response.json(), ), ) @@ -350,24 +383,29 @@ def investors( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def funds( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[ServiceProvidersFundsResponse]: """ Get funds that have engaged this service provider. Returns fund formation and compliance work with engagement details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[ServiceProvidersFundsResponse] Successful response """ _response = self._client_wrapper.httpx_client.request( @@ -378,9 +416,9 @@ def funds( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersFundsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersFundsResponse, # type: ignore object_=_response.json(), ), ) @@ -421,25 +459,29 @@ def funds( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def limited_partners( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: + ) -> HttpResponse[None]: """ - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. + **Coming Soon** - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response + HttpResponse[None] """ _response = self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/service-providers/{jsonable_encoder(provider_id)}/limited-partners", @@ -448,14 +490,7 @@ def limited_partners( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -492,66 +527,10 @@ def limited_partners( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def updates( - self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to service provider profile data. Returns history of changes including new engagements, office openings, and team changes with timestamps. - - Parameters - ---------- - provider_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/service-providers/{jsonable_encoder(provider_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) @@ -562,33 +541,38 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def search( self, *, + q: str, limit: typing.Optional[int] = None, provider_type: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[ServiceProvidersSearchResponse]: """ Search for service providers including law firms, accounting firms, investment banks, and consultants. Returns matching provider profiles with specializations and notable clients. Parameters ---------- + q : str + Provider name or keyword (e.g., 'Wilson Sonsini') + limit : typing.Optional[int] - Maximum results + Maximum number of results to return (1-100, default: 10) provider_type : typing.Optional[str] - Provider type filter + Filter by provider type (e.g., 'law', 'accounting', 'investment_bank', 'consulting') request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[ServiceProvidersSearchResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( "v2/datasets/odyssey/service-providers/search", method="GET", params={ + "q": q, "limit": limit, "provider_type": provider_type, }, @@ -597,9 +581,9 @@ async def search( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersSearchResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersSearchResponse, # type: ignore object_=_response.json(), ), ) @@ -618,24 +602,29 @@ async def search( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def bio( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[ServiceProvidersBioResponse]: """ Get comprehensive service provider profile including firm description, practice areas, office locations, and notable work. This is the primary endpoint for provider overview data. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[ServiceProvidersBioResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -646,9 +635,9 @@ async def bio( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersBioResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersBioResponse, # type: ignore object_=_response.json(), ), ) @@ -678,24 +667,29 @@ async def bio( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def companies( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[ServiceProvidersCompaniesResponse]: """ Get companies that have engaged this service provider. Returns client list with engagement types and sectors served. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[ServiceProvidersCompaniesResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -706,9 +700,9 @@ async def companies( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersCompaniesResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersCompaniesResponse, # type: ignore object_=_response.json(), ), ) @@ -749,24 +743,29 @@ async def companies( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def deals( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[ServiceProvidersDealsResponse]: """ Get deals where this provider was involved as advisor, counsel, or banker. Returns transaction history with roles and deal values. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[ServiceProvidersDealsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -777,9 +776,9 @@ async def deals( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersDealsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersDealsResponse, # type: ignore object_=_response.json(), ), ) @@ -820,24 +819,29 @@ async def deals( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def investors( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[ServiceProvidersInvestorsResponse]: """ Get investors that have engaged this service provider. Returns investor clients with engagement types and fund formation work. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[ServiceProvidersInvestorsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -848,9 +852,9 @@ async def investors( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersInvestorsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersInvestorsResponse, # type: ignore object_=_response.json(), ), ) @@ -891,24 +895,29 @@ async def investors( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def funds( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[ServiceProvidersFundsResponse]: """ Get funds that have engaged this service provider. Returns fund formation and compliance work with engagement details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + AsyncHttpResponse[ServiceProvidersFundsResponse] Successful response """ _response = await self._client_wrapper.httpx_client.request( @@ -919,9 +928,9 @@ async def funds( try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + ServiceProvidersFundsResponse, parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore + type_=ServiceProvidersFundsResponse, # type: ignore object_=_response.json(), ), ) @@ -962,25 +971,29 @@ async def funds( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def limited_partners( self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + ) -> AsyncHttpResponse[None]: """ - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. + **Coming Soon** - Get limited partners that have engaged this service provider. Returns LP clients with service types and relationship details. Parameters ---------- provider_id : str + Service provider entity ID request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response + AsyncHttpResponse[None] """ _response = await self._client_wrapper.httpx_client.request( f"v2/datasets/odyssey/service-providers/{jsonable_encoder(provider_id)}/limited-partners", @@ -989,14 +1002,7 @@ async def limited_partners( ) try: if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, data=None) if _response.status_code == 401: raise UnauthorizedError( headers=dict(_response.headers), @@ -1033,64 +1039,8 @@ async def limited_partners( _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def updates( - self, provider_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: - """ - Get changelog of updates to service provider profile data. Returns history of changes including new engagements, office openings, and team changes with timestamps. - - Parameters - ---------- - provider_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] - Successful response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v2/datasets/odyssey/service-providers/{jsonable_encoder(provider_id)}/updates", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Any], - parse_obj_as( - type_=typing.Dict[str, typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/runcaptain/service_providers/types/__init__.py b/src/runcaptain/service_providers/types/__init__.py new file mode 100644 index 0000000..397803d --- /dev/null +++ b/src/runcaptain/service_providers/types/__init__.py @@ -0,0 +1,73 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .service_providers_bio_response import ServiceProvidersBioResponse + from .service_providers_companies_response import ServiceProvidersCompaniesResponse + from .service_providers_companies_response_companies_item import ServiceProvidersCompaniesResponseCompaniesItem + from .service_providers_deals_response import ServiceProvidersDealsResponse + from .service_providers_deals_response_deals_item import ServiceProvidersDealsResponseDealsItem + from .service_providers_funds_response import ServiceProvidersFundsResponse + from .service_providers_funds_response_funds_item import ServiceProvidersFundsResponseFundsItem + from .service_providers_investors_response import ServiceProvidersInvestorsResponse + from .service_providers_investors_response_investors_item import ServiceProvidersInvestorsResponseInvestorsItem + from .service_providers_search_response import ServiceProvidersSearchResponse + from .service_providers_search_response_results_item import ServiceProvidersSearchResponseResultsItem + from .service_providers_search_response_results_item_location import ( + ServiceProvidersSearchResponseResultsItemLocation, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ServiceProvidersBioResponse": ".service_providers_bio_response", + "ServiceProvidersCompaniesResponse": ".service_providers_companies_response", + "ServiceProvidersCompaniesResponseCompaniesItem": ".service_providers_companies_response_companies_item", + "ServiceProvidersDealsResponse": ".service_providers_deals_response", + "ServiceProvidersDealsResponseDealsItem": ".service_providers_deals_response_deals_item", + "ServiceProvidersFundsResponse": ".service_providers_funds_response", + "ServiceProvidersFundsResponseFundsItem": ".service_providers_funds_response_funds_item", + "ServiceProvidersInvestorsResponse": ".service_providers_investors_response", + "ServiceProvidersInvestorsResponseInvestorsItem": ".service_providers_investors_response_investors_item", + "ServiceProvidersSearchResponse": ".service_providers_search_response", + "ServiceProvidersSearchResponseResultsItem": ".service_providers_search_response_results_item", + "ServiceProvidersSearchResponseResultsItemLocation": ".service_providers_search_response_results_item_location", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ServiceProvidersBioResponse", + "ServiceProvidersCompaniesResponse", + "ServiceProvidersCompaniesResponseCompaniesItem", + "ServiceProvidersDealsResponse", + "ServiceProvidersDealsResponseDealsItem", + "ServiceProvidersFundsResponse", + "ServiceProvidersFundsResponseFundsItem", + "ServiceProvidersInvestorsResponse", + "ServiceProvidersInvestorsResponseInvestorsItem", + "ServiceProvidersSearchResponse", + "ServiceProvidersSearchResponseResultsItem", + "ServiceProvidersSearchResponseResultsItemLocation", +] diff --git a/src/runcaptain/service_providers/types/service_providers_bio_response.py b/src/runcaptain/service_providers/types/service_providers_bio_response.py new file mode 100644 index 0000000..a50afb6 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_bio_response.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ServiceProvidersBioResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider entity ID + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider name + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service type (law, accounting, etc.) + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider description + """ + + headquarters: typing.Optional[str] = pydantic.Field(default=None) + """ + Headquarters location + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + data_sources: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_companies_response.py b/src/runcaptain/service_providers/types/service_providers_companies_response.py new file mode 100644 index 0000000..658ebef --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_companies_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .service_providers_companies_response_companies_item import ServiceProvidersCompaniesResponseCompaniesItem + + +class ServiceProvidersCompaniesResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider entity ID + """ + + companies: typing.Optional[typing.List[ServiceProvidersCompaniesResponseCompaniesItem]] = None + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total companies served + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_companies_response_companies_item.py b/src/runcaptain/service_providers/types/service_providers_companies_response_companies_item.py new file mode 100644 index 0000000..72d15c1 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_companies_response_companies_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ServiceProvidersCompaniesResponseCompaniesItem(UniversalBaseModel): + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + company_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Company entity ID + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service provided + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_deals_response.py b/src/runcaptain/service_providers/types/service_providers_deals_response.py new file mode 100644 index 0000000..85e0fac --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_deals_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .service_providers_deals_response_deals_item import ServiceProvidersDealsResponseDealsItem + + +class ServiceProvidersDealsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider entity ID + """ + + deals: typing.Optional[typing.List[ServiceProvidersDealsResponseDealsItem]] = None + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total deals + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_deals_response_deals_item.py b/src/runcaptain/service_providers/types/service_providers_deals_response_deals_item.py new file mode 100644 index 0000000..b757179 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_deals_response_deals_item.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ServiceProvidersDealsResponseDealsItem(UniversalBaseModel): + deal_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal identifier + """ + + company_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name + """ + + deal_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Deal type + """ + + role: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider role in deal + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_funds_response.py b/src/runcaptain/service_providers/types/service_providers_funds_response.py new file mode 100644 index 0000000..ce604d7 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_funds_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .service_providers_funds_response_funds_item import ServiceProvidersFundsResponseFundsItem + + +class ServiceProvidersFundsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider entity ID + """ + + funds: typing.Optional[typing.List[ServiceProvidersFundsResponseFundsItem]] = None + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total fund clients + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_funds_response_funds_item.py b/src/runcaptain/service_providers/types/service_providers_funds_response_funds_item.py new file mode 100644 index 0000000..79a7763 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_funds_response_funds_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ServiceProvidersFundsResponseFundsItem(UniversalBaseModel): + fund_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund name + """ + + fund_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Fund entity ID + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service provided + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_investors_response.py b/src/runcaptain/service_providers/types/service_providers_investors_response.py new file mode 100644 index 0000000..941b3af --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_investors_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .service_providers_investors_response_investors_item import ServiceProvidersInvestorsResponseInvestorsItem + + +class ServiceProvidersInvestorsResponse(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider entity ID + """ + + investors: typing.Optional[typing.List[ServiceProvidersInvestorsResponseInvestorsItem]] = None + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total investor clients + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_investors_response_investors_item.py b/src/runcaptain/service_providers/types/service_providers_investors_response_investors_item.py new file mode 100644 index 0000000..922897f --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_investors_response_investors_item.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ServiceProvidersInvestorsResponseInvestorsItem(UniversalBaseModel): + investor_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor name + """ + + investor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Investor entity ID + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service provided + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_search_response.py b/src/runcaptain/service_providers/types/service_providers_search_response.py new file mode 100644 index 0000000..7389d20 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_search_response.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .service_providers_search_response_results_item import ServiceProvidersSearchResponseResultsItem + + +class ServiceProvidersSearchResponse(UniversalBaseModel): + query: typing.Optional[str] = pydantic.Field(default=None) + """ + The search query that was executed + """ + + results: typing.Optional[typing.List[ServiceProvidersSearchResponseResultsItem]] = pydantic.Field(default=None) + """ + Array of matching results + """ + + total_results: typing.Optional[int] = pydantic.Field(default=None) + """ + Total number of matching results + """ + + page: typing.Optional[int] = pydantic.Field(default=None) + """ + Current page number + """ + + page_size: typing.Optional[int] = pydantic.Field(default=None) + """ + Results per page + """ + + total: typing.Optional[int] = pydantic.Field(default=None) + """ + Total matching results + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Results limit + """ + + provider_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider type filter that was applied + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_search_response_results_item.py b/src/runcaptain/service_providers/types/service_providers_search_response_results_item.py new file mode 100644 index 0000000..4a05af6 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_search_response_results_item.py @@ -0,0 +1,78 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .service_providers_search_response_results_item_location import ServiceProvidersSearchResponseResultsItemLocation + + +class ServiceProvidersSearchResponseResultsItem(UniversalBaseModel): + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Unique entity identifier + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider name + """ + + service_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Service type (law, accounting, etc.) + """ + + headquarters: typing.Optional[str] = pydantic.Field(default=None) + """ + Headquarters location + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Brief description + """ + + provider_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider type (law, accounting, etc.) + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name + """ + + industry: typing.Optional[str] = pydantic.Field(default=None) + """ + Industry classification + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Website URL + """ + + employee_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of employees + """ + + location: typing.Optional[ServiceProvidersSearchResponseResultsItemLocation] = pydantic.Field(default=None) + """ + HQ location + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Data source + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/service_providers/types/service_providers_search_response_results_item_location.py b/src/runcaptain/service_providers/types/service_providers_search_response_results_item_location.py new file mode 100644 index 0000000..5cf4084 --- /dev/null +++ b/src/runcaptain/service_providers/types/service_providers_search_response_results_item_location.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ServiceProvidersSearchResponseResultsItemLocation(UniversalBaseModel): + """ + HQ location + """ + + locality: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/__init__.py b/src/runcaptain/types/__init__.py index 467be36..acc5047 100644 --- a/src/runcaptain/types/__init__.py +++ b/src/runcaptain/types/__init__.py @@ -13,6 +13,7 @@ from .collection_item_v2 import CollectionItemV2 from .collection_list_response_v2 import CollectionListResponseV2 from .collection_response_v2 import CollectionResponseV2 + from .conflict_error_body import ConflictErrorBody from .dataset_article_response import DatasetArticleResponse from .dataset_search_response import DatasetSearchResponse from .dataset_search_result import DatasetSearchResult @@ -31,9 +32,12 @@ from .job_progress import JobProgress from .job_progress_current_stage import JobProgressCurrentStage from .job_result import JobResult + from .job_rollback_response_v2 import JobRollbackResponseV2 from .job_status import JobStatus from .job_status_response_v2 import JobStatusResponseV2 from .job_status_response_v2job_type import JobStatusResponseV2JobType + from .not_implemented_error_body import NotImplementedErrorBody + from .not_implemented_error_body_details import NotImplementedErrorBodyDetails from .query_response_v2 import QueryResponseV2 from .query_stream_complete_event import QueryStreamCompleteEvent from .query_stream_error_event import QueryStreamErrorEvent @@ -48,10 +52,13 @@ from .query_stream_text_event import QueryStreamTextEvent from .query_stream_tool_end_event import QueryStreamToolEndEvent from .query_stream_tool_start_event import QueryStreamToolStartEvent - from .relevant_document_v2 import RelevantDocumentV2 + from .scientific_ask_response import ScientificAskResponse + from .scientific_source import ScientificSource from .search_result import SearchResult from .standard_response_v2 import StandardResponseV2 from .token_balance import TokenBalance + from .validate_parsing_script_response_v2 import ValidateParsingScriptResponseV2 + from .validate_parsing_script_response_v2error_type import ValidateParsingScriptResponseV2ErrorType from .validation_error import ValidationError from .validation_error_loc_item import ValidationErrorLocItem _dynamic_imports: typing.Dict[str, str] = { @@ -62,6 +69,7 @@ "CollectionItemV2": ".collection_item_v2", "CollectionListResponseV2": ".collection_list_response_v2", "CollectionResponseV2": ".collection_response_v2", + "ConflictErrorBody": ".conflict_error_body", "DatasetArticleResponse": ".dataset_article_response", "DatasetSearchResponse": ".dataset_search_response", "DatasetSearchResult": ".dataset_search_result", @@ -80,9 +88,12 @@ "JobProgress": ".job_progress", "JobProgressCurrentStage": ".job_progress_current_stage", "JobResult": ".job_result", + "JobRollbackResponseV2": ".job_rollback_response_v2", "JobStatus": ".job_status", "JobStatusResponseV2": ".job_status_response_v2", "JobStatusResponseV2JobType": ".job_status_response_v2job_type", + "NotImplementedErrorBody": ".not_implemented_error_body", + "NotImplementedErrorBodyDetails": ".not_implemented_error_body_details", "QueryResponseV2": ".query_response_v2", "QueryStreamCompleteEvent": ".query_stream_complete_event", "QueryStreamErrorEvent": ".query_stream_error_event", @@ -95,10 +106,13 @@ "QueryStreamTextEvent": ".query_stream_text_event", "QueryStreamToolEndEvent": ".query_stream_tool_end_event", "QueryStreamToolStartEvent": ".query_stream_tool_start_event", - "RelevantDocumentV2": ".relevant_document_v2", + "ScientificAskResponse": ".scientific_ask_response", + "ScientificSource": ".scientific_source", "SearchResult": ".search_result", "StandardResponseV2": ".standard_response_v2", "TokenBalance": ".token_balance", + "ValidateParsingScriptResponseV2": ".validate_parsing_script_response_v2", + "ValidateParsingScriptResponseV2ErrorType": ".validate_parsing_script_response_v2error_type", "ValidationError": ".validation_error", "ValidationErrorLocItem": ".validation_error_loc_item", } @@ -133,6 +147,7 @@ def __dir__(): "CollectionItemV2", "CollectionListResponseV2", "CollectionResponseV2", + "ConflictErrorBody", "DatasetArticleResponse", "DatasetSearchResponse", "DatasetSearchResult", @@ -151,9 +166,12 @@ def __dir__(): "JobProgress", "JobProgressCurrentStage", "JobResult", + "JobRollbackResponseV2", "JobStatus", "JobStatusResponseV2", "JobStatusResponseV2JobType", + "NotImplementedErrorBody", + "NotImplementedErrorBodyDetails", "QueryResponseV2", "QueryStreamCompleteEvent", "QueryStreamErrorEvent", @@ -166,10 +184,13 @@ def __dir__(): "QueryStreamTextEvent", "QueryStreamToolEndEvent", "QueryStreamToolStartEvent", - "RelevantDocumentV2", + "ScientificAskResponse", + "ScientificSource", "SearchResult", "StandardResponseV2", "TokenBalance", + "ValidateParsingScriptResponseV2", + "ValidateParsingScriptResponseV2ErrorType", "ValidationError", "ValidationErrorLocItem", ] diff --git a/src/runcaptain/types/conflict_error_body.py b/src/runcaptain/types/conflict_error_body.py new file mode 100644 index 0000000..92678e3 --- /dev/null +++ b/src/runcaptain/types/conflict_error_body.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ConflictErrorBody(UniversalBaseModel): + detail: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/dataset_search_result.py b/src/runcaptain/types/dataset_search_result.py index d07dd28..8541089 100644 --- a/src/runcaptain/types/dataset_search_result.py +++ b/src/runcaptain/types/dataset_search_result.py @@ -36,6 +36,11 @@ class DatasetSearchResult(UniversalBaseModel): Source display name """ + author: typing.Optional[str] = pydantic.Field(default=None) + """ + Article author/byline if available + """ + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/runcaptain/types/job_cancel_response_v2.py b/src/runcaptain/types/job_cancel_response_v2.py index 50ca29f..e4c083b 100644 --- a/src/runcaptain/types/job_cancel_response_v2.py +++ b/src/runcaptain/types/job_cancel_response_v2.py @@ -11,6 +11,10 @@ class JobCancelResponseV2(UniversalBaseModel): status: str message: str cancelled_at: typing.Optional[str] = None + cleanup_initiated: typing.Optional[bool] = pydantic.Field(default=None) + """ + True if background data cleanup (RDS + Turbopuffer) was started + """ if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/runcaptain/types/job_rollback_response_v2.py b/src/runcaptain/types/job_rollback_response_v2.py new file mode 100644 index 0000000..d7e8e6f --- /dev/null +++ b/src/runcaptain/types/job_rollback_response_v2.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class JobRollbackResponseV2(UniversalBaseModel): + job_id: str = pydantic.Field() + """ + The job ID that was rolled back + """ + + status: str = pydantic.Field() + """ + Rollback status: rolled_back or error + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable summary of the rollback + """ + + files_removed: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of file names that were removed by the rollback + """ + + rolled_back_at: typing.Optional[str] = pydantic.Field(default=None) + """ + RFC3339 timestamp of when the rollback completed + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/not_implemented_error_body.py b/src/runcaptain/types/not_implemented_error_body.py new file mode 100644 index 0000000..ba75926 --- /dev/null +++ b/src/runcaptain/types/not_implemented_error_body.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .not_implemented_error_body_details import NotImplementedErrorBodyDetails + + +class NotImplementedErrorBody(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Always false for errors + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Error code (NOT_IMPLEMENTED) + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable error message + """ + + details: typing.Optional[NotImplementedErrorBodyDetails] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/not_implemented_error_body_details.py b/src/runcaptain/types/not_implemented_error_body_details.py new file mode 100644 index 0000000..48f2d33 --- /dev/null +++ b/src/runcaptain/types/not_implemented_error_body_details.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class NotImplementedErrorBodyDetails(UniversalBaseModel): + endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + The requested endpoint path + """ + + reason: typing.Optional[str] = pydantic.Field(default=None) + """ + Why this endpoint is not yet available + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/query_response_v2.py b/src/runcaptain/types/query_response_v2.py index e54fb91..68d5be4 100644 --- a/src/runcaptain/types/query_response_v2.py +++ b/src/runcaptain/types/query_response_v2.py @@ -4,7 +4,6 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .relevant_document_v2 import RelevantDocumentV2 from .search_result import SearchResult from .token_balance import TokenBalance @@ -29,11 +28,6 @@ class QueryResponseV2(UniversalBaseModel): Alias for summary (v1 compatibility) """ - relevant_documents: typing.Optional[typing.List[RelevantDocumentV2]] = pydantic.Field(default=None) - """ - List of relevant documents (when inference=true) - """ - inference: typing.Optional[bool] = pydantic.Field(default=None) """ Whether inference mode was used @@ -41,7 +35,7 @@ class QueryResponseV2(UniversalBaseModel): search_results: typing.Optional[typing.List[SearchResult]] = pydantic.Field(default=None) """ - Raw search results with content (when inference=false) + Search results with chunk content, scores, and source URIs. Returned for both inference=true (the chunks used as context) and inference=false (raw search results). """ total_results: typing.Optional[int] = pydantic.Field(default=None) diff --git a/src/runcaptain/types/relevant_document_v2.py b/src/runcaptain/types/relevant_document_v2.py deleted file mode 100644 index 003de07..0000000 --- a/src/runcaptain/types/relevant_document_v2.py +++ /dev/null @@ -1,41 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class RelevantDocumentV2(UniversalBaseModel): - """ - A relevant document returned for inference=true queries - """ - - relevancy_score: typing.Optional[float] = pydantic.Field(default=None) - """ - Relevancy score from 0 to 1 - """ - - document_id: typing.Optional[str] = pydantic.Field(default=None) - """ - Unique identifier for the document - """ - - filename: typing.Optional[str] = pydantic.Field(default=None) - """ - Name of the document file - """ - - excerpt: typing.Optional[str] = pydantic.Field(default=None) - """ - Relevant excerpt from the document - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/scientific_ask_response.py b/src/runcaptain/types/scientific_ask_response.py new file mode 100644 index 0000000..9bdc75f --- /dev/null +++ b/src/runcaptain/types/scientific_ask_response.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .scientific_source import ScientificSource + + +class ScientificAskResponse(UniversalBaseModel): + """ + Response for POST /v2/datasets/scientific/medical/ask (non-streaming). + """ + + domain: str = pydantic.Field() + """ + The scientific domain, e.g. 'medical'. + """ + + answer: str = pydantic.Field() + """ + Synthesized answer with inline citations. + """ + + sources: typing.Optional[typing.List[ScientificSource]] = None + gaps: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Things the agent could not find or verify. + """ + + tool_calls: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of tool calls the agent made. + """ + + latency_ms: typing.Optional[int] = pydantic.Field(default=None) + """ + End-to-end wall-clock latency. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/scientific_source.py b/src/runcaptain/types/scientific_source.py new file mode 100644 index 0000000..52bb49b --- /dev/null +++ b/src/runcaptain/types/scientific_source.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ScientificSource(UniversalBaseModel): + """ + A single cited source surfaced by the agent. + """ + + type: str = pydantic.Field() + """ + pubmed | pmc_full_text | clinical_trial | semantic_scholar + """ + + pmid: typing.Optional[str] = None + journal: typing.Optional[str] = None + year: typing.Optional[int] = None + doi: typing.Optional[str] = None + pmcid: typing.Optional[str] = None + quoted: typing.Optional[str] = None + nct_id: typing.Optional[str] = None + phase: typing.Optional[str] = None + status: typing.Optional[str] = None + paper_id: typing.Optional[str] = None + citation_count: typing.Optional[int] = None + title: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/search_result.py b/src/runcaptain/types/search_result.py index 628febc..99ff9f8 100644 --- a/src/runcaptain/types/search_result.py +++ b/src/runcaptain/types/search_result.py @@ -8,7 +8,7 @@ class SearchResult(UniversalBaseModel): """ - Individual search result for inference=false queries + Individual search result with chunk content, scores, and source URI """ score: typing.Optional[float] = pydantic.Field(default=None) @@ -31,6 +31,11 @@ class SearchResult(UniversalBaseModel): Name of the source file """ + uri: typing.Optional[str] = pydantic.Field(default=None) + """ + Source URI of the document. External files show their original URI (e.g., s3://bucket/key). Files uploaded via Captain show captain://org-id/object-key. + """ + chunk_index: typing.Optional[int] = pydantic.Field(default=None) """ Index of this chunk within the document @@ -48,7 +53,7 @@ class SearchResult(UniversalBaseModel): rerank_score: typing.Optional[float] = pydantic.Field(default=None) """ - Voyage rerank-2.5 relevance score when rerank=true was used + Reranker relevance score (Gemini Flash 2.5 or Voyage rerank-2.5) when rerank=true was used """ metadata: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) diff --git a/src/runcaptain/types/validate_parsing_script_response_v2.py b/src/runcaptain/types/validate_parsing_script_response_v2.py new file mode 100644 index 0000000..4f12282 --- /dev/null +++ b/src/runcaptain/types/validate_parsing_script_response_v2.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .validate_parsing_script_response_v2error_type import ValidateParsingScriptResponseV2ErrorType + + +class ValidateParsingScriptResponseV2(UniversalBaseModel): + valid: bool = pydantic.Field() + """ + True if the script passes all validation checks. + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable error message. Null when valid=true. + """ + + error_type: typing.Optional[ValidateParsingScriptResponseV2ErrorType] = pydantic.Field(default=None) + """ + Error category. Null when valid=true. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/runcaptain/types/validate_parsing_script_response_v2error_type.py b/src/runcaptain/types/validate_parsing_script_response_v2error_type.py new file mode 100644 index 0000000..629034f --- /dev/null +++ b/src/runcaptain/types/validate_parsing_script_response_v2error_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ValidateParsingScriptResponseV2ErrorType = typing.Union[typing.Literal["syntax_error", "no_export"], typing.Any]