From 3cb0d28a494079d22cdb2330c628dc5ee9f51fa7 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 21:05:19 -0700 Subject: [PATCH 01/12] Ship generated synchronous and asynchronous Python SDK 0.3 --- .github/allowed_signers | 1 + .github/workflows/ci.yml | 19 + .github/workflows/publish.yml | 2 +- .gitignore | 3 + README.md | 87 +- VERSION | 1 + fern/fern.config.json | 4 + fern/generators.yml | 14 + fern/method-names.json | 549 + fern/openapi.json | 23649 ++++++++++++++++ pyproject.toml | 5 +- reference.md | 9563 +++++++ scripts/check-commit-identity.sh | 30 + scripts/generate.sh | 9 + scripts/install-generated.py | 32 + scripts/prepare-openapi.py | 94 + src/arcmira/__init__.py | 1469 +- src/arcmira/_default_clients.py | 30 + src/arcmira/_package.py | 8 + src/arcmira/channels/__init__.py | 109 + src/arcmira/channels/client.py | 282 + src/arcmira/channels/guests/__init__.py | 38 + src/arcmira/channels/guests/client.py | 218 + src/arcmira/channels/guests/raw_client.py | 397 + src/arcmira/channels/guests/types/__init__.py | 40 + .../list_guests_request_is_appearance.py | 5 + .../guests/types/list_guests_request_mode.py | 5 + .../guests/types/list_guests_request_order.py | 5 + src/arcmira/channels/raw_client.py | 512 + src/arcmira/channels/related/__init__.py | 82 + src/arcmira/channels/related/client.py | 930 + src/arcmira/channels/related/raw_client.py | 1861 ++ .../channels/related/types/__init__.py | 80 + .../channels_related_request_is_appearance.py | 5 + .../types/channels_related_request_mode.py | 5 + .../types/channels_related_request_order.py | 5 + ...nizations_related_request_is_appearance.py | 5 + .../organizations_related_request_mode.py | 5 + .../organizations_related_request_order.py | 5 + .../people_related_request_is_appearance.py | 5 + .../types/people_related_request_mode.py | 5 + .../types/people_related_request_order.py | 5 + .../products_related_request_is_appearance.py | 5 + .../types/products_related_request_mode.py | 5 + .../types/products_related_request_order.py | 5 + .../topics_related_request_is_appearance.py | 5 + .../types/topics_related_request_mode.py | 5 + .../types/topics_related_request_order.py | 5 + src/arcmira/channels/sponsors/__init__.py | 34 + src/arcmira/channels/sponsors/client.py | 158 + src/arcmira/channels/sponsors/raw_client.py | 324 + .../channels/sponsors/types/__init__.py | 38 + .../types/list_sponsors_request_src.py | 5 + .../types/list_sponsors_request_status.py | 5 + src/arcmira/channels/types/__init__.py | 34 + .../types/coverage_channels_request_src.py | 5 + src/arcmira/channels/videos/__init__.py | 34 + src/arcmira/channels/videos/client.py | 188 + src/arcmira/channels/videos/raw_client.py | 361 + src/arcmira/channels/videos/types/__init__.py | 34 + .../videos/types/list_videos_request_src.py | 5 + src/arcmira/client.py | 654 + src/arcmira/core/__init__.py | 132 + src/arcmira/core/api_error.py | 23 + src/arcmira/core/client_wrapper.py | 147 + src/arcmira/core/datetime_utils.py | 70 + src/arcmira/core/file.py | 67 + src/arcmira/core/force_multipart.py | 18 + src/arcmira/core/http_client.py | 940 + src/arcmira/core/http_response.py | 63 + src/arcmira/core/http_sse/__init__.py | 42 + src/arcmira/core/http_sse/_api.py | 455 + src/arcmira/core/http_sse/_decoders.py | 74 + src/arcmira/core/http_sse/_exceptions.py | 7 + src/arcmira/core/http_sse/_models.py | 17 + src/arcmira/core/jsonable_encoder.py | 133 + src/arcmira/core/logging.py | 107 + src/arcmira/core/pagination.py | 82 + src/arcmira/core/parse_error.py | 36 + src/arcmira/core/pydantic_utilities.py | 486 + src/arcmira/core/query_encoder.py | 58 + src/arcmira/core/remove_none_from_dict.py | 11 + src/arcmira/core/request_options.py | 40 + src/arcmira/core/serialization.py | 347 + src/arcmira/corrections/__init__.py | 37 + src/arcmira/corrections/client.py | 404 + src/arcmira/corrections/raw_client.py | 1025 + src/arcmira/corrections/types/__init__.py | 38 + .../submit_corrections_request_anchor.py | 38 + .../types/submit_corrections_request_kind.py | 8 + src/arcmira/entities/__init__.py | 72 + src/arcmira/entities/client.py | 643 + src/arcmira/entities/mentions/__init__.py | 38 + src/arcmira/entities/mentions/client.py | 232 + src/arcmira/entities/mentions/raw_client.py | 417 + .../entities/mentions/types/__init__.py | 40 + .../types/list_mentions_request_details.py | 5 + .../types/list_mentions_request_sentiment.py | 5 + .../types/list_mentions_request_src.py | 5 + src/arcmira/entities/raw_client.py | 1587 ++ .../entities/recommendations/__init__.py | 37 + .../entities/recommendations/client.py | 223 + .../entities/recommendations/raw_client.py | 406 + .../recommendations/types/__init__.py | 38 + ...t_recommendations_request_mention_class.py | 7 + .../types/list_recommendations_request_src.py | 5 + src/arcmira/entities/types/__init__.py | 53 + .../types/lookup_entities_request_type.py | 7 + .../types/momentum_entities_request_src.py | 5 + .../types/resolve_entities_request_src.py | 5 + .../types/resolve_entities_request_type.py | 7 + .../types/search_entities_request_src.py | 5 + .../types/search_entities_request_type.py | 7 + src/arcmira/environment.py | 7 + src/arcmira/errors/__init__.py | 68 + src/arcmira/errors/bad_request_error.py | 11 + src/arcmira/errors/conflict_error.py | 11 + src/arcmira/errors/forbidden_error.py | 11 + src/arcmira/errors/internal_server_error.py | 11 + src/arcmira/errors/not_found_error.py | 11 + src/arcmira/errors/payment_required_error.py | 11 + .../errors/precondition_failed_error.py | 11 + .../errors/service_unavailable_error.py | 11 + src/arcmira/errors/too_many_requests_error.py | 11 + src/arcmira/errors/unauthorized_error.py | 11 + .../errors/unprocessable_entity_error.py | 11 + src/arcmira/feedback/__init__.py | 58 + src/arcmira/feedback/client.py | 285 + src/arcmira/feedback/raw_client.py | 602 + src/arcmira/feedback/types/__init__.py | 58 + ...ubmit_feedback_request_corrections_item.py | 44 + ...ack_request_corrections_item_issue_type.py | 27 + ..._request_corrections_item_mention_class.py | 7 + ...eedback_request_corrections_item_reason.py | 16 + ...quest_corrections_item_suggested_change.py | 27 + .../types/submit_feedback_request_method.py | 5 + .../types/submit_feedback_request_type.py | 18 + src/arcmira/health/__init__.py | 3 + src/arcmira/health/client.py | 96 + src/arcmira/health/raw_client.py | 144 + src/arcmira/me/__init__.py | 37 + src/arcmira/me/client.py | 187 + src/arcmira/me/raw_client.py | 486 + src/arcmira/me/types/__init__.py | 38 + .../update_settings_me_request_transcripts.py | 37 + ...settings_me_request_transcripts_quality.py | 5 + src/arcmira/mentions/__init__.py | 55 + src/arcmira/mentions/client.py | 408 + src/arcmira/mentions/raw_client.py | 773 + src/arcmira/mentions/types/__init__.py | 53 + .../types/count_mentions_request_mode.py | 5 + .../types/count_mentions_request_src.py | 5 + .../types/list_mentions_request_details.py | 5 + .../list_mentions_request_entity_type.py | 7 + .../types/list_mentions_request_sentiment.py | 5 + .../types/list_mentions_request_src.py | 5 + src/arcmira/meta/__init__.py | 3 + src/arcmira/meta/client.py | 263 + src/arcmira/meta/raw_client.py | 612 + src/arcmira/monitors/__init__.py | 40 + src/arcmira/monitors/alerts/__init__.py | 3 + src/arcmira/monitors/alerts/client.py | 118 + src/arcmira/monitors/alerts/raw_client.py | 259 + src/arcmira/monitors/client.py | 743 + src/arcmira/monitors/raw_client.py | 1528 + src/arcmira/monitors/trackers/__init__.py | 3 + src/arcmira/monitors/trackers/client.py | 214 + src/arcmira/monitors/trackers/raw_client.py | 528 + src/arcmira/monitors/types/__init__.py | 38 + ...reate_monitors_request_notify_frequency.py | 5 + ...pdate_monitors_request_notify_frequency.py | 5 + src/arcmira/organizations/__init__.py | 85 + src/arcmira/organizations/client.py | 133 + src/arcmira/organizations/raw_client.py | 268 + src/arcmira/organizations/related/__init__.py | 82 + src/arcmira/organizations/related/client.py | 930 + .../organizations/related/raw_client.py | 1861 ++ .../organizations/related/types/__init__.py | 80 + .../channels_related_request_is_appearance.py | 5 + .../types/channels_related_request_mode.py | 5 + .../types/channels_related_request_order.py | 5 + ...nizations_related_request_is_appearance.py | 5 + .../organizations_related_request_mode.py | 5 + .../organizations_related_request_order.py | 5 + .../people_related_request_is_appearance.py | 5 + .../types/people_related_request_mode.py | 5 + .../types/people_related_request_order.py | 5 + .../products_related_request_is_appearance.py | 5 + .../types/products_related_request_mode.py | 5 + .../types/products_related_request_order.py | 5 + .../topics_related_request_is_appearance.py | 5 + .../types/topics_related_request_mode.py | 5 + .../types/topics_related_request_order.py | 5 + src/arcmira/people/__init__.py | 94 + src/arcmira/people/appearances/__init__.py | 38 + src/arcmira/people/appearances/client.py | 218 + src/arcmira/people/appearances/raw_client.py | 397 + .../people/appearances/types/__init__.py | 40 + .../list_appearances_request_is_appearance.py | 5 + .../types/list_appearances_request_mode.py | 5 + .../types/list_appearances_request_order.py | 5 + src/arcmira/people/client.py | 150 + src/arcmira/people/raw_client.py | 268 + src/arcmira/people/related/__init__.py | 82 + src/arcmira/people/related/client.py | 930 + src/arcmira/people/related/raw_client.py | 1861 ++ src/arcmira/people/related/types/__init__.py | 80 + .../channels_related_request_is_appearance.py | 5 + .../types/channels_related_request_mode.py | 5 + .../types/channels_related_request_order.py | 5 + ...nizations_related_request_is_appearance.py | 5 + .../organizations_related_request_mode.py | 5 + .../organizations_related_request_order.py | 5 + .../people_related_request_is_appearance.py | 5 + .../types/people_related_request_mode.py | 5 + .../types/people_related_request_order.py | 5 + .../products_related_request_is_appearance.py | 5 + .../types/products_related_request_mode.py | 5 + .../types/products_related_request_order.py | 5 + .../topics_related_request_is_appearance.py | 5 + .../types/topics_related_request_mode.py | 5 + .../types/topics_related_request_order.py | 5 + src/arcmira/products/__init__.py | 85 + src/arcmira/products/client.py | 131 + src/arcmira/products/raw_client.py | 268 + src/arcmira/products/related/__init__.py | 82 + src/arcmira/products/related/client.py | 930 + src/arcmira/products/related/raw_client.py | 1861 ++ .../products/related/types/__init__.py | 80 + .../channels_related_request_is_appearance.py | 5 + .../types/channels_related_request_mode.py | 5 + .../types/channels_related_request_order.py | 5 + ...nizations_related_request_is_appearance.py | 5 + .../organizations_related_request_mode.py | 5 + .../organizations_related_request_order.py | 5 + .../people_related_request_is_appearance.py | 5 + .../types/people_related_request_mode.py | 5 + .../types/people_related_request_order.py | 5 + .../products_related_request_is_appearance.py | 5 + .../types/products_related_request_mode.py | 5 + .../types/products_related_request_order.py | 5 + .../topics_related_request_is_appearance.py | 5 + .../types/topics_related_request_mode.py | 5 + .../types/topics_related_request_order.py | 5 + src/arcmira/py.typed | 0 src/arcmira/raw_client.py | 294 + src/arcmira/recommendations/__init__.py | 46 + src/arcmira/recommendations/client.py | 234 + src/arcmira/recommendations/raw_client.py | 426 + src/arcmira/recommendations/types/__init__.py | 44 + ...ist_recommendations_request_entity_type.py | 7 + ...t_recommendations_request_mention_class.py | 7 + .../types/list_recommendations_request_src.py | 5 + src/arcmira/team/__init__.py | 34 + src/arcmira/team/client.py | 186 + src/arcmira/team/raw_client.py | 451 + src/arcmira/team/usage_events/__init__.py | 3 + src/arcmira/team/usage_events/client.py | 143 + src/arcmira/team/usage_events/raw_client.py | 302 + src/arcmira/topics/__init__.py | 85 + src/arcmira/topics/client.py | 131 + src/arcmira/topics/raw_client.py | 268 + src/arcmira/topics/related/__init__.py | 82 + src/arcmira/topics/related/client.py | 930 + src/arcmira/topics/related/raw_client.py | 1861 ++ src/arcmira/topics/related/types/__init__.py | 80 + .../channels_related_request_is_appearance.py | 5 + .../types/channels_related_request_mode.py | 5 + .../types/channels_related_request_order.py | 5 + ...nizations_related_request_is_appearance.py | 5 + .../organizations_related_request_mode.py | 5 + .../organizations_related_request_order.py | 5 + .../people_related_request_is_appearance.py | 5 + .../types/people_related_request_mode.py | 5 + .../types/people_related_request_order.py | 5 + .../products_related_request_is_appearance.py | 5 + .../types/products_related_request_mode.py | 5 + .../types/products_related_request_order.py | 5 + .../topics_related_request_is_appearance.py | 5 + .../types/topics_related_request_mode.py | 5 + .../types/topics_related_request_order.py | 5 + src/arcmira/trackers/__init__.py | 49 + src/arcmira/trackers/alerts/__init__.py | 3 + src/arcmira/trackers/alerts/client.py | 118 + src/arcmira/trackers/alerts/raw_client.py | 259 + src/arcmira/trackers/client.py | 614 + src/arcmira/trackers/raw_client.py | 1248 + src/arcmira/trackers/types/__init__.py | 44 + .../create_trackers_request_entity_type.py | 7 + ...eate_trackers_request_person_match_mode.py | 5 + ...date_trackers_request_person_match_mode.py | 5 + src/arcmira/transcripts/__init__.py | 65 + src/arcmira/transcripts/client.py | 963 + src/arcmira/transcripts/edits/__init__.py | 3 + src/arcmira/transcripts/edits/client.py | 260 + src/arcmira/transcripts/edits/raw_client.py | 560 + src/arcmira/transcripts/merges/__init__.py | 3 + src/arcmira/transcripts/merges/client.py | 329 + src/arcmira/transcripts/merges/raw_client.py | 777 + src/arcmira/transcripts/raw_client.py | 2122 ++ src/arcmira/transcripts/speakers/__init__.py | 3 + src/arcmira/transcripts/speakers/client.py | 264 + .../transcripts/speakers/raw_client.py | 568 + src/arcmira/transcripts/types/__init__.py | 56 + .../types/captions_transcripts_request_src.py | 5 + .../types/get_transcripts_request_quality.py | 5 + .../types/get_transcripts_request_src.py | 5 + .../list_requests_transcripts_request_src.py | 5 + .../search_transcripts_request_source.py | 7 + .../types/search_transcripts_request_src.py | 5 + .../types/status_transcripts_request_src.py | 5 + src/arcmira/types/__init__.py | 1239 + src/arcmira/types/account_settings.py | 24 + src/arcmira/types/alert.py | 110 + src/arcmira/types/alert_evidence_kind.py | 5 + src/arcmira/types/alert_list_response.py | 33 + src/arcmira/types/alert_monitor.py | 31 + src/arcmira/types/alert_tracker.py | 41 + src/arcmira/types/bad_ranking_change.py | 31 + src/arcmira/types/caption_track.py | 32 + .../types/channel_coverage_response.py | 24 + .../channel_coverage_response_channel.py | 43 + ...el_coverage_response_channel_source_mix.py | 25 + .../types/channel_guest_list_response.py | 75 + ...guest_list_response_export_capabilities.py | 69 + .../channel_guest_list_response_items_item.py | 60 + ...uest_list_response_items_item_sentiment.py | 5 + src/arcmira/types/channel_page_response.py | 103 + .../channel_page_response_channel_info.py | 72 + .../types/channel_page_response_entity.py | 122 + .../channel_page_response_entity_owner.py | 41 + .../channel_page_response_entity_type.py | 5 + ...el_page_response_episodes_by_month_item.py | 36 + .../channel_page_response_episodes_item.py | 130 + ...el_page_response_episodes_item_platform.py | 5 + ...l_page_response_episodes_item_sentiment.py | 5 + ...l_page_response_episodes_item_timestamp.py | 5 + ...hannel_page_response_episodes_item_type.py | 5 + .../channel_page_response_guests_item.py | 61 + .../channel_page_response_guests_item_role.py | 5 + ...nel_page_response_guests_item_sentiment.py | 5 + ...annel_page_response_hosts_detailed_item.py | 60 + ..._response_hosts_detailed_item_sentiment.py | 5 + ...hannel_page_response_organizations_item.py | 55 + ...e_response_organizations_item_sentiment.py | 7 + .../channel_page_response_products_item.py | 55 + ...l_page_response_products_item_sentiment.py | 5 + ...l_page_response_recommendations_summary.py | 31 + .../types/channel_page_response_stats.py | 41 + .../channel_page_response_topics_item.py | 33 + ...nel_page_response_topics_item_sentiment.py | 5 + src/arcmira/types/channel_sponsor.py | 49 + src/arcmira/types/channel_sponsor_entity.py | 46 + .../types/channel_sponsor_sponsor_status.py | 41 + .../types/channel_sponsors_response.py | 33 + .../types/channel_sponsors_response_access.py | 68 + .../channel_sponsors_response_access_gate.py | 7 + ...channel_sponsors_response_access_reason.py | 5 + .../channel_sponsors_response_access_type.py | 17 + ...channel_sponsors_response_access_unlock.py | 42 + ..._sponsors_response_access_unlock_action.py | 36 + .../channel_sponsors_response_channel.py | 37 + .../types/channel_sponsors_response_meta.py | 32 + src/arcmira/types/channel_videos_response.py | 56 + .../types/channel_videos_response_channel.py | 33 + .../channel_videos_response_episodes_item.py | 50 + .../types/correction_accepted_response.py | 33 + .../correction_accepted_response_kind.py | 8 + .../types/correction_seq_mismatch_response.py | 36 + src/arcmira/types/delivery_issue_change.py | 27 + .../types/delivery_issue_change_channel.py | 5 + src/arcmira/types/entity.py | 107 + src/arcmira/types/entity_card.py | 77 + src/arcmira/types/entity_cards_response.py | 23 + .../types/entity_channel_list_response.py | 75 + ...annel_list_response_export_capabilities.py | 69 + ...entity_channel_list_response_items_item.py | 54 + .../entity_detail_recommendations_summary.py | 51 + src/arcmira/types/entity_detail_response.py | 22 + src/arcmira/types/entity_lookup_response.py | 20 + src/arcmira/types/entity_momentum_response.py | 60 + .../types/entity_momentum_response_access.py | 68 + .../entity_momentum_response_access_gate.py | 7 + .../entity_momentum_response_access_reason.py | 5 + .../entity_momentum_response_access_type.py | 17 + .../entity_momentum_response_access_unlock.py | 42 + ..._momentum_response_access_unlock_action.py | 36 + ...ntity_momentum_response_paid_vs_organic.py | 25 + ...entity_momentum_response_top_shows_item.py | 26 + .../types/entity_momentum_response_verdict.py | 5 + .../types/entity_momentum_response_volume.py | 51 + .../entity_organization_list_response.py | 75 + ...ation_list_response_export_capabilities.py | 69 + ...y_organization_list_response_items_item.py | 65 + ...tion_list_response_items_item_sentiment.py | 7 + src/arcmira/types/entity_page_mention.py | 158 + .../types/entity_page_mention_excerpt.py | 106 + ...age_mention_excerpt_public_source_class.py | 7 + .../types/entity_page_mention_platform.py | 5 + .../types/entity_page_mention_sentiment.py | 5 + src/arcmira/types/entity_page_mention_type.py | 5 + .../types/entity_people_list_response.py | 88 + ...eople_list_response_export_capabilities.py | 69 + .../entity_people_list_response_items_item.py | 60 + ...ople_list_response_items_item_sentiment.py | 5 + ...entity_people_list_response_people_mode.py | 5 + .../types/entity_product_list_response.py | 75 + ...oduct_list_response_export_capabilities.py | 69 + ...entity_product_list_response_items_item.py | 77 + ...duct_list_response_items_item_sentiment.py | 7 + src/arcmira/types/entity_ref.py | 42 + src/arcmira/types/entity_resolve_response.py | 53 + .../types/entity_resolve_response_ask.py | 25 + ...ntity_resolve_response_ask_options_item.py | 25 + .../entity_resolve_response_confidence.py | 7 + src/arcmira/types/entity_search_response.py | 29 + src/arcmira/types/entity_search_result.py | 73 + ...y_search_result_recommendations_summary.py | 36 + .../types/entity_topic_list_response.py | 75 + ...topic_list_response_export_capabilities.py | 69 + .../entity_topic_list_response_items_item.py | 38 + ...opic_list_response_items_item_sentiment.py | 5 + src/arcmira/types/error.py | 34 + src/arcmira/types/error_error.py | 64 + src/arcmira/types/error_error_gate.py | 7 + src/arcmira/types/error_error_reason.py | 5 + src/arcmira/types/error_error_type.py | 17 + src/arcmira/types/error_error_unlock.py | 42 + .../types/error_error_unlock_action.py | 36 + src/arcmira/types/exposure_meta.py | 313 + src/arcmira/types/exposure_meta_access.py | 92 + .../types/exposure_meta_access_chart.py | 26 + src/arcmira/types/exposure_meta_access_cls.py | 5 + .../types/exposure_meta_access_freshness.py | 37 + .../types/exposure_meta_access_ladder.py | 5 + .../types/exposure_meta_access_rows.py | 39 + .../exposure_meta_access_rows_entities.py | 36 + .../types/exposure_meta_access_rows_media.py | 36 + .../types/exposure_meta_access_rows_topics.py | 36 + .../types/exposure_meta_access_unlock.py | 51 + ...xposure_meta_access_unlock_limit_action.py | 15 + .../types/exposure_meta_access_unlock_src.py | 5 + .../types/exposure_meta_access_view.py | 5 + .../exposure_meta_access_withheld_item.py | 68 + ...exposure_meta_access_withheld_item_kind.py | 7 + ...xposure_meta_access_withheld_item_param.py | 5 + ...osure_meta_access_withheld_item_section.py | 5 + ...exposure_meta_access_withheld_item_what.py | 7 + src/arcmira/types/exposure_meta_credits.py | 41 + .../types/exposure_meta_credits_on_demand.py | 32 + .../types/exposure_meta_credits_plan.py | 32 + src/arcmira/types/exposure_meta_free_limit.py | 31 + .../types/exposure_meta_limit_action.py | 15 + src/arcmira/types/exposure_meta_limits.py | 70 + .../types/exposure_meta_recent_preview.py | 89 + ...exposure_meta_recent_preview_experiment.py | 5 + .../exposure_meta_recent_preview_mentions.py | 89 + ...meta_recent_preview_mentions_experiment.py | 5 + ...re_meta_recent_preview_mentions_subject.py | 5 + ...cent_preview_mentions_teaser_items_item.py | 54 + .../exposure_meta_recent_preview_subject.py | 5 + ...e_meta_recent_preview_teaser_items_item.py | 54 + src/arcmira/types/exposure_meta_totals.py | 71 + .../types/exposure_meta_usage_limit_type.py | 5 + .../types/feedback_correction_result.py | 64 + ...edback_correction_result_recommendation.py | 105 + ..._correction_result_recommendation_media.py | 47 + ...ult_recommendation_media_source_channel.py | 31 + .../feedback_correction_result_status.py | 7 + .../types/feedback_readback_correction.py | 63 + .../feedback_readback_correction_status.py | 17 + .../types/feedback_readback_response.py | 54 + .../feedback_readback_response_status.py | 17 + src/arcmira/types/feedback_response.py | 58 + .../types/freeform_suggested_change.py | 8 + src/arcmira/types/health_response.py | 34 + src/arcmira/types/health_response_status.py | 5 + src/arcmira/types/health_response_version.py | 5 + src/arcmira/types/me_response.py | 73 + .../types/me_response_credential_kind.py | 5 + src/arcmira/types/me_response_usage.py | 49 + .../types/me_response_usage_credits.py | 41 + .../me_response_usage_credits_on_demand.py | 32 + .../types/me_response_usage_credits_plan.py | 32 + src/arcmira/types/me_response_usage_hits.py | 36 + src/arcmira/types/me_settings_response.py | 20 + src/arcmira/types/mention.py | 98 + src/arcmira/types/mention_counts_response.py | 77 + .../types/mention_counts_response_mode.py | 5 + .../mention_counts_response_rows_item.py | 66 + .../mention_counts_response_shared_item.py | 48 + ...ts_response_shared_item_by_channel_item.py | 43 + src/arcmira/types/mention_list_response.py | 46 + .../types/mention_list_response_entity.py | 111 + .../types/mention_list_response_unlock.py | 31 + src/arcmira/types/mention_media.py | 58 + .../types/mention_media_source_channel.py | 36 + src/arcmira/types/mention_recommendations.py | 27 + src/arcmira/types/mention_sentiment.py | 5 + src/arcmira/types/merge_suggestion_change.py | 68 + src/arcmira/types/message_response.py | 22 + src/arcmira/types/missed_alert_change.py | 36 + src/arcmira/types/missing_result_change.py | 36 + src/arcmira/types/monitor.py | 240 + .../types/monitor_add_trackers_response.py | 42 + src/arcmira/types/monitor_delete_response.py | 35 + .../types/monitor_email_recipients_item.py | 29 + ...email_recipients_item_invitation_status.py | 7 + .../monitor_email_recipients_item_status.py | 8 + src/arcmira/types/monitor_list_response.py | 23 + .../monitor_list_response_monitors_item.py | 54 + ...esponse_monitors_item_slack_integration.py | 36 + .../types/monitor_mutation_response.py | 24 + .../monitor_mutation_response_monitor.py | 43 + .../types/monitor_trackers_response.py | 28 + ...monitor_trackers_response_trackers_item.py | 105 + src/arcmira/types/named_entity_ref.py | 32 + src/arcmira/types/open_api_document.py | 48 + src/arcmira/types/open_api_document_info.py | 37 + .../types/open_api_document_info_contact.py | 31 + .../types/open_api_document_servers_item.py | 22 + .../types/organization_page_response.py | 89 + ...rganization_page_response_channels_item.py | 60 + ...n_page_response_channels_item_sentiment.py | 7 + .../organization_page_response_entity.py | 99 + ...age_response_entity_owned_channels_item.py | 82 + ...age_response_entity_owned_products_item.py | 82 + .../organization_page_response_entity_type.py | 5 + ...on_page_response_mentions_by_month_item.py | 36 + .../organization_page_response_people_item.py | 55 + ...ion_page_response_people_item_sentiment.py | 7 + ...rganization_page_response_products_item.py | 55 + ...n_page_response_products_item_sentiment.py | 5 + .../organization_page_response_role_edge.py | 137 + ...anization_page_response_role_edge_label.py | 5 + ...ion_page_response_role_edge_people_item.py | 63 + ...ponse_role_edge_recent_appearances_item.py | 61 + ...ganization_page_response_role_edge_role.py | 5 + .../types/organization_page_response_stats.py | 84 + .../organization_page_response_topics_item.py | 33 + ...ion_page_response_topics_item_sentiment.py | 7 + .../types/person_appearance_list_response.py | 62 + ...son_appearance_list_response_items_item.py | 134 + ...rance_list_response_items_item_platform.py | 5 + ...ance_list_response_items_item_sentiment.py | 7 + ...ppearance_list_response_items_item_type.py | 5 + src/arcmira/types/person_page_response.py | 106 + ...page_response_appearances_by_month_item.py | 36 + .../person_page_response_appearances_item.py | 134 + ...page_response_appearances_item_platform.py | 5 + ...age_response_appearances_item_sentiment.py | 5 + ...son_page_response_appearances_item_type.py | 5 + .../types/person_page_response_brands_item.py | 55 + ...son_page_response_brands_item_sentiment.py | 5 + .../types/person_page_response_entity.py | 132 + ...age_response_entity_owned_channels_item.py | 82 + ...age_response_entity_owned_products_item.py | 82 + ...on_page_response_mentions_by_month_item.py | 36 + .../person_page_response_mentions_item.py | 134 + ...on_page_response_mentions_item_platform.py | 5 + ...n_page_response_mentions_item_sentiment.py | 5 + ...person_page_response_mentions_item_type.py | 5 + .../types/person_page_response_people_item.py | 61 + .../person_page_response_people_item_role.py | 5 + ...son_page_response_people_item_sentiment.py | 5 + .../person_page_response_products_item.py | 55 + ...n_page_response_products_item_sentiment.py | 5 + .../types/person_page_response_role_edge.py | 71 + .../person_page_response_role_edge_label.py | 5 + .../person_page_response_role_edge_role.py | 5 + .../types/person_page_response_stats.py | 91 + .../types/person_page_response_topics_item.py | 33 + ...son_page_response_topics_item_sentiment.py | 5 + src/arcmira/types/product_page_response.py | 83 + .../product_page_response_channels_item.py | 60 + ...t_page_response_channels_item_sentiment.py | 5 + .../types/product_page_response_entity.py | 92 + .../product_page_response_entity_owner.py | 41 + ...product_page_response_entity_parent_org.py | 31 + .../product_page_response_entity_type.py | 5 + ...ct_page_response_mentions_by_month_item.py | 36 + .../product_page_response_opportunities.py | 37 + ...roduct_page_response_organizations_item.py | 55 + ...e_response_organizations_item_sentiment.py | 7 + .../product_page_response_people_item.py | 55 + ...uct_page_response_people_item_sentiment.py | 5 + .../types/product_page_response_stats.py | 84 + .../product_page_response_topics_item.py | 33 + ...uct_page_response_topics_item_sentiment.py | 5 + src/arcmira/types/published_excerpt.py | 106 + .../published_excerpt_public_source_class.py | 7 + src/arcmira/types/recommendation.py | 101 + .../types/recommendation_enrichment_item.py | 67 + .../types/recommendation_list_response.py | 35 + .../recommendation_list_response_entity.py | 111 + src/arcmira/types/recommendation_media.py | 43 + .../recommendation_media_source_channel.py | 31 + src/arcmira/types/resolve_candidate.py | 67 + src/arcmira/types/resolve_candidate_match.py | 5 + src/arcmira/types/resolve_suggestion.py | 83 + src/arcmira/types/resolve_suggestion_match.py | 5 + .../types/resolve_suggestion_reason.py | 7 + src/arcmira/types/search_request_type.py | 5 + src/arcmira/types/search_resolve_response.py | 46 + .../types/search_resolve_response_entity.py | 36 + src/arcmira/types/signup_sent_response.py | 28 + .../types/signup_sent_response_next.py | 32 + .../types/signup_sent_response_next_method.py | 5 + src/arcmira/types/signup_verified_response.py | 57 + ...eaker_identification_submitted_response.py | 25 + ...ation_submitted_response_identification.py | 64 + ...ubmitted_response_identification_entity.py | 36 + ...ubmitted_response_identification_status.py | 7 + src/arcmira/types/stale_metadata_change.py | 36 + src/arcmira/types/team_member.py | 49 + src/arcmira/types/team_member_role.py | 5 + src/arcmira/types/team_member_seat_type.py | 5 + src/arcmira/types/team_member_spend.py | 59 + src/arcmira/types/team_member_spend_role.py | 5 + .../types/team_member_spend_seat_type.py | 5 + src/arcmira/types/team_members_response.py | 26 + .../types/team_members_response_team.py | 27 + src/arcmira/types/team_spend_response.py | 28 + src/arcmira/types/team_usage_event.py | 67 + .../types/team_usage_events_response.py | 33 + src/arcmira/types/topic_page_response.py | 87 + .../topic_page_response_channels_item.py | 60 + ...c_page_response_channels_item_sentiment.py | 5 + .../topic_page_response_companies_item.py | 55 + ..._page_response_companies_item_sentiment.py | 5 + .../types/topic_page_response_entity.py | 68 + .../types/topic_page_response_entity_type.py | 5 + ...ic_page_response_mentions_by_month_item.py | 36 + .../topic_page_response_products_item.py | 55 + ...c_page_response_products_item_sentiment.py | 5 + ...topic_page_response_related_topics_item.py | 33 + ..._response_related_topics_item_sentiment.py | 7 + .../types/topic_page_response_stats.py | 94 + .../types/topic_page_response_voices_item.py | 61 + .../topic_page_response_voices_item_role.py | 5 + ...pic_page_response_voices_item_sentiment.py | 5 + src/arcmira/types/tracker.py | 211 + src/arcmira/types/tracker_list_response.py | 28 + .../types/tracker_mutation_response.py | 24 + .../transcript_edit_submitted_response.py | 23 + ...transcript_edit_submitted_response_edit.py | 61 + ...ipt_edit_submitted_response_edit_status.py | 7 + src/arcmira/types/transcript_pending.py | 24 + .../types/transcript_pending_premium_job.py | 22 + .../types/transcript_pending_quality.py | 5 + .../types/transcript_purchase_quote.py | 33 + ...transcript_purchase_quote_billing_scope.py | 5 + .../types/transcript_purchase_quote_charge.py | 21 + .../transcript_purchase_quote_charge_unit.py | 5 + src/arcmira/types/transcript_quote.py | 27 + src/arcmira/types/transcript_response.py | 98 + .../types/transcript_response_access.py | 68 + .../types/transcript_response_access_gate.py | 7 + .../transcript_response_access_reason.py | 5 + .../types/transcript_response_access_type.py | 17 + .../transcript_response_access_unlock.py | 42 + ...ranscript_response_access_unlock_action.py | 36 + .../types/transcript_response_lines_item.py | 38 + .../transcript_response_paragraphs_item.py | 25 + .../types/transcript_response_premium_job.py | 41 + .../types/transcript_response_quality.py | 5 + .../types/transcript_response_range.py | 24 + .../types/transcript_response_source.py | 7 + .../transcript_response_speakers_item.py | 37 + src/arcmira/types/transcript_result.py | 71 + src/arcmira/types/transcript_search_chunk.py | 162 + .../types/transcript_search_response.py | 82 + .../transcript_search_response_access.py | 68 + .../transcript_search_response_access_gate.py | 7 + ...ranscript_search_response_access_reason.py | 5 + .../transcript_search_response_access_type.py | 17 + ...ranscript_search_response_access_unlock.py | 42 + ...pt_search_response_access_unlock_action.py | 36 + .../transcript_search_response_filters.py | 64 + ...transcript_search_response_search_index.py | 32 + ...ript_search_response_search_index_state.py | 5 + src/arcmira/types/transcript_settings.py | 37 + .../types/transcript_settings_quality.py | 5 + src/arcmira/types/transcript_video.py | 48 + .../types/transcription_list_response.py | 33 + ...anscription_list_response_requests_item.py | 23 + src/arcmira/types/transcription_request.py | 113 + .../types/transcription_request_charge.py | 26 + .../transcription_request_charge_unit.py | 5 + .../types/transcription_request_quote.py | 31 + .../types/transcription_request_stage.py | 5 + .../types/transcription_request_state.py | 5 + .../types/transcription_request_status.py | 10 + .../types/transcription_submit_response.py | 38 + src/arcmira/types/video_captions_response.py | 25 + .../types/video_merge_list_response.py | 23 + .../video_merge_list_response_merges_item.py | 84 + ..._merge_list_response_merges_item_status.py | 7 + .../types/video_merge_submitted_response.py | 23 + .../video_merge_submitted_response_merge.py | 70 + ...o_merge_submitted_response_merge_status.py | 7 + .../types/webhook_secret_rotate_response.py | 55 + src/arcmira/types/withdrawn_response.py | 22 + .../types/wrong_classification_change.py | 27 + ...ong_classification_change_mention_class.py | 5 + src/arcmira/types/wrong_entity_change.py | 36 + src/arcmira/types/wrong_entity_type_change.py | 32 + .../types/wrong_entity_type_change_field.py | 5 + tests/fixtures/transcription-responses.json | 263 + tests/test_generation.py | 30 + tests/test_transcription.py | 138 + uv.lock | 327 + 712 files changed, 101973 insertions(+), 29 deletions(-) create mode 100644 .github/allowed_signers create mode 100644 .github/workflows/ci.yml create mode 100644 VERSION create mode 100644 fern/fern.config.json create mode 100644 fern/generators.yml create mode 100644 fern/method-names.json create mode 100644 fern/openapi.json create mode 100644 reference.md create mode 100755 scripts/check-commit-identity.sh create mode 100755 scripts/generate.sh create mode 100644 scripts/install-generated.py create mode 100644 scripts/prepare-openapi.py create mode 100644 src/arcmira/_default_clients.py create mode 100644 src/arcmira/_package.py create mode 100644 src/arcmira/channels/__init__.py create mode 100644 src/arcmira/channels/client.py create mode 100644 src/arcmira/channels/guests/__init__.py create mode 100644 src/arcmira/channels/guests/client.py create mode 100644 src/arcmira/channels/guests/raw_client.py create mode 100644 src/arcmira/channels/guests/types/__init__.py create mode 100644 src/arcmira/channels/guests/types/list_guests_request_is_appearance.py create mode 100644 src/arcmira/channels/guests/types/list_guests_request_mode.py create mode 100644 src/arcmira/channels/guests/types/list_guests_request_order.py create mode 100644 src/arcmira/channels/raw_client.py create mode 100644 src/arcmira/channels/related/__init__.py create mode 100644 src/arcmira/channels/related/client.py create mode 100644 src/arcmira/channels/related/raw_client.py create mode 100644 src/arcmira/channels/related/types/__init__.py create mode 100644 src/arcmira/channels/related/types/channels_related_request_is_appearance.py create mode 100644 src/arcmira/channels/related/types/channels_related_request_mode.py create mode 100644 src/arcmira/channels/related/types/channels_related_request_order.py create mode 100644 src/arcmira/channels/related/types/organizations_related_request_is_appearance.py create mode 100644 src/arcmira/channels/related/types/organizations_related_request_mode.py create mode 100644 src/arcmira/channels/related/types/organizations_related_request_order.py create mode 100644 src/arcmira/channels/related/types/people_related_request_is_appearance.py create mode 100644 src/arcmira/channels/related/types/people_related_request_mode.py create mode 100644 src/arcmira/channels/related/types/people_related_request_order.py create mode 100644 src/arcmira/channels/related/types/products_related_request_is_appearance.py create mode 100644 src/arcmira/channels/related/types/products_related_request_mode.py create mode 100644 src/arcmira/channels/related/types/products_related_request_order.py create mode 100644 src/arcmira/channels/related/types/topics_related_request_is_appearance.py create mode 100644 src/arcmira/channels/related/types/topics_related_request_mode.py create mode 100644 src/arcmira/channels/related/types/topics_related_request_order.py create mode 100644 src/arcmira/channels/sponsors/__init__.py create mode 100644 src/arcmira/channels/sponsors/client.py create mode 100644 src/arcmira/channels/sponsors/raw_client.py create mode 100644 src/arcmira/channels/sponsors/types/__init__.py create mode 100644 src/arcmira/channels/sponsors/types/list_sponsors_request_src.py create mode 100644 src/arcmira/channels/sponsors/types/list_sponsors_request_status.py create mode 100644 src/arcmira/channels/types/__init__.py create mode 100644 src/arcmira/channels/types/coverage_channels_request_src.py create mode 100644 src/arcmira/channels/videos/__init__.py create mode 100644 src/arcmira/channels/videos/client.py create mode 100644 src/arcmira/channels/videos/raw_client.py create mode 100644 src/arcmira/channels/videos/types/__init__.py create mode 100644 src/arcmira/channels/videos/types/list_videos_request_src.py create mode 100644 src/arcmira/client.py create mode 100644 src/arcmira/core/__init__.py create mode 100644 src/arcmira/core/api_error.py create mode 100644 src/arcmira/core/client_wrapper.py create mode 100644 src/arcmira/core/datetime_utils.py create mode 100644 src/arcmira/core/file.py create mode 100644 src/arcmira/core/force_multipart.py create mode 100644 src/arcmira/core/http_client.py create mode 100644 src/arcmira/core/http_response.py create mode 100644 src/arcmira/core/http_sse/__init__.py create mode 100644 src/arcmira/core/http_sse/_api.py create mode 100644 src/arcmira/core/http_sse/_decoders.py create mode 100644 src/arcmira/core/http_sse/_exceptions.py create mode 100644 src/arcmira/core/http_sse/_models.py create mode 100644 src/arcmira/core/jsonable_encoder.py create mode 100644 src/arcmira/core/logging.py create mode 100644 src/arcmira/core/pagination.py create mode 100644 src/arcmira/core/parse_error.py create mode 100644 src/arcmira/core/pydantic_utilities.py create mode 100644 src/arcmira/core/query_encoder.py create mode 100644 src/arcmira/core/remove_none_from_dict.py create mode 100644 src/arcmira/core/request_options.py create mode 100644 src/arcmira/core/serialization.py create mode 100644 src/arcmira/corrections/__init__.py create mode 100644 src/arcmira/corrections/client.py create mode 100644 src/arcmira/corrections/raw_client.py create mode 100644 src/arcmira/corrections/types/__init__.py create mode 100644 src/arcmira/corrections/types/submit_corrections_request_anchor.py create mode 100644 src/arcmira/corrections/types/submit_corrections_request_kind.py create mode 100644 src/arcmira/entities/__init__.py create mode 100644 src/arcmira/entities/client.py create mode 100644 src/arcmira/entities/mentions/__init__.py create mode 100644 src/arcmira/entities/mentions/client.py create mode 100644 src/arcmira/entities/mentions/raw_client.py create mode 100644 src/arcmira/entities/mentions/types/__init__.py create mode 100644 src/arcmira/entities/mentions/types/list_mentions_request_details.py create mode 100644 src/arcmira/entities/mentions/types/list_mentions_request_sentiment.py create mode 100644 src/arcmira/entities/mentions/types/list_mentions_request_src.py create mode 100644 src/arcmira/entities/raw_client.py create mode 100644 src/arcmira/entities/recommendations/__init__.py create mode 100644 src/arcmira/entities/recommendations/client.py create mode 100644 src/arcmira/entities/recommendations/raw_client.py create mode 100644 src/arcmira/entities/recommendations/types/__init__.py create mode 100644 src/arcmira/entities/recommendations/types/list_recommendations_request_mention_class.py create mode 100644 src/arcmira/entities/recommendations/types/list_recommendations_request_src.py create mode 100644 src/arcmira/entities/types/__init__.py create mode 100644 src/arcmira/entities/types/lookup_entities_request_type.py create mode 100644 src/arcmira/entities/types/momentum_entities_request_src.py create mode 100644 src/arcmira/entities/types/resolve_entities_request_src.py create mode 100644 src/arcmira/entities/types/resolve_entities_request_type.py create mode 100644 src/arcmira/entities/types/search_entities_request_src.py create mode 100644 src/arcmira/entities/types/search_entities_request_type.py create mode 100644 src/arcmira/environment.py create mode 100644 src/arcmira/errors/__init__.py create mode 100644 src/arcmira/errors/bad_request_error.py create mode 100644 src/arcmira/errors/conflict_error.py create mode 100644 src/arcmira/errors/forbidden_error.py create mode 100644 src/arcmira/errors/internal_server_error.py create mode 100644 src/arcmira/errors/not_found_error.py create mode 100644 src/arcmira/errors/payment_required_error.py create mode 100644 src/arcmira/errors/precondition_failed_error.py create mode 100644 src/arcmira/errors/service_unavailable_error.py create mode 100644 src/arcmira/errors/too_many_requests_error.py create mode 100644 src/arcmira/errors/unauthorized_error.py create mode 100644 src/arcmira/errors/unprocessable_entity_error.py create mode 100644 src/arcmira/feedback/__init__.py create mode 100644 src/arcmira/feedback/client.py create mode 100644 src/arcmira/feedback/raw_client.py create mode 100644 src/arcmira/feedback/types/__init__.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_corrections_item.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_corrections_item_issue_type.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_corrections_item_mention_class.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_corrections_item_reason.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_corrections_item_suggested_change.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_method.py create mode 100644 src/arcmira/feedback/types/submit_feedback_request_type.py create mode 100644 src/arcmira/health/__init__.py create mode 100644 src/arcmira/health/client.py create mode 100644 src/arcmira/health/raw_client.py create mode 100644 src/arcmira/me/__init__.py create mode 100644 src/arcmira/me/client.py create mode 100644 src/arcmira/me/raw_client.py create mode 100644 src/arcmira/me/types/__init__.py create mode 100644 src/arcmira/me/types/update_settings_me_request_transcripts.py create mode 100644 src/arcmira/me/types/update_settings_me_request_transcripts_quality.py create mode 100644 src/arcmira/mentions/__init__.py create mode 100644 src/arcmira/mentions/client.py create mode 100644 src/arcmira/mentions/raw_client.py create mode 100644 src/arcmira/mentions/types/__init__.py create mode 100644 src/arcmira/mentions/types/count_mentions_request_mode.py create mode 100644 src/arcmira/mentions/types/count_mentions_request_src.py create mode 100644 src/arcmira/mentions/types/list_mentions_request_details.py create mode 100644 src/arcmira/mentions/types/list_mentions_request_entity_type.py create mode 100644 src/arcmira/mentions/types/list_mentions_request_sentiment.py create mode 100644 src/arcmira/mentions/types/list_mentions_request_src.py create mode 100644 src/arcmira/meta/__init__.py create mode 100644 src/arcmira/meta/client.py create mode 100644 src/arcmira/meta/raw_client.py create mode 100644 src/arcmira/monitors/__init__.py create mode 100644 src/arcmira/monitors/alerts/__init__.py create mode 100644 src/arcmira/monitors/alerts/client.py create mode 100644 src/arcmira/monitors/alerts/raw_client.py create mode 100644 src/arcmira/monitors/client.py create mode 100644 src/arcmira/monitors/raw_client.py create mode 100644 src/arcmira/monitors/trackers/__init__.py create mode 100644 src/arcmira/monitors/trackers/client.py create mode 100644 src/arcmira/monitors/trackers/raw_client.py create mode 100644 src/arcmira/monitors/types/__init__.py create mode 100644 src/arcmira/monitors/types/create_monitors_request_notify_frequency.py create mode 100644 src/arcmira/monitors/types/update_monitors_request_notify_frequency.py create mode 100644 src/arcmira/organizations/__init__.py create mode 100644 src/arcmira/organizations/client.py create mode 100644 src/arcmira/organizations/raw_client.py create mode 100644 src/arcmira/organizations/related/__init__.py create mode 100644 src/arcmira/organizations/related/client.py create mode 100644 src/arcmira/organizations/related/raw_client.py create mode 100644 src/arcmira/organizations/related/types/__init__.py create mode 100644 src/arcmira/organizations/related/types/channels_related_request_is_appearance.py create mode 100644 src/arcmira/organizations/related/types/channels_related_request_mode.py create mode 100644 src/arcmira/organizations/related/types/channels_related_request_order.py create mode 100644 src/arcmira/organizations/related/types/organizations_related_request_is_appearance.py create mode 100644 src/arcmira/organizations/related/types/organizations_related_request_mode.py create mode 100644 src/arcmira/organizations/related/types/organizations_related_request_order.py create mode 100644 src/arcmira/organizations/related/types/people_related_request_is_appearance.py create mode 100644 src/arcmira/organizations/related/types/people_related_request_mode.py create mode 100644 src/arcmira/organizations/related/types/people_related_request_order.py create mode 100644 src/arcmira/organizations/related/types/products_related_request_is_appearance.py create mode 100644 src/arcmira/organizations/related/types/products_related_request_mode.py create mode 100644 src/arcmira/organizations/related/types/products_related_request_order.py create mode 100644 src/arcmira/organizations/related/types/topics_related_request_is_appearance.py create mode 100644 src/arcmira/organizations/related/types/topics_related_request_mode.py create mode 100644 src/arcmira/organizations/related/types/topics_related_request_order.py create mode 100644 src/arcmira/people/__init__.py create mode 100644 src/arcmira/people/appearances/__init__.py create mode 100644 src/arcmira/people/appearances/client.py create mode 100644 src/arcmira/people/appearances/raw_client.py create mode 100644 src/arcmira/people/appearances/types/__init__.py create mode 100644 src/arcmira/people/appearances/types/list_appearances_request_is_appearance.py create mode 100644 src/arcmira/people/appearances/types/list_appearances_request_mode.py create mode 100644 src/arcmira/people/appearances/types/list_appearances_request_order.py create mode 100644 src/arcmira/people/client.py create mode 100644 src/arcmira/people/raw_client.py create mode 100644 src/arcmira/people/related/__init__.py create mode 100644 src/arcmira/people/related/client.py create mode 100644 src/arcmira/people/related/raw_client.py create mode 100644 src/arcmira/people/related/types/__init__.py create mode 100644 src/arcmira/people/related/types/channels_related_request_is_appearance.py create mode 100644 src/arcmira/people/related/types/channels_related_request_mode.py create mode 100644 src/arcmira/people/related/types/channels_related_request_order.py create mode 100644 src/arcmira/people/related/types/organizations_related_request_is_appearance.py create mode 100644 src/arcmira/people/related/types/organizations_related_request_mode.py create mode 100644 src/arcmira/people/related/types/organizations_related_request_order.py create mode 100644 src/arcmira/people/related/types/people_related_request_is_appearance.py create mode 100644 src/arcmira/people/related/types/people_related_request_mode.py create mode 100644 src/arcmira/people/related/types/people_related_request_order.py create mode 100644 src/arcmira/people/related/types/products_related_request_is_appearance.py create mode 100644 src/arcmira/people/related/types/products_related_request_mode.py create mode 100644 src/arcmira/people/related/types/products_related_request_order.py create mode 100644 src/arcmira/people/related/types/topics_related_request_is_appearance.py create mode 100644 src/arcmira/people/related/types/topics_related_request_mode.py create mode 100644 src/arcmira/people/related/types/topics_related_request_order.py create mode 100644 src/arcmira/products/__init__.py create mode 100644 src/arcmira/products/client.py create mode 100644 src/arcmira/products/raw_client.py create mode 100644 src/arcmira/products/related/__init__.py create mode 100644 src/arcmira/products/related/client.py create mode 100644 src/arcmira/products/related/raw_client.py create mode 100644 src/arcmira/products/related/types/__init__.py create mode 100644 src/arcmira/products/related/types/channels_related_request_is_appearance.py create mode 100644 src/arcmira/products/related/types/channels_related_request_mode.py create mode 100644 src/arcmira/products/related/types/channels_related_request_order.py create mode 100644 src/arcmira/products/related/types/organizations_related_request_is_appearance.py create mode 100644 src/arcmira/products/related/types/organizations_related_request_mode.py create mode 100644 src/arcmira/products/related/types/organizations_related_request_order.py create mode 100644 src/arcmira/products/related/types/people_related_request_is_appearance.py create mode 100644 src/arcmira/products/related/types/people_related_request_mode.py create mode 100644 src/arcmira/products/related/types/people_related_request_order.py create mode 100644 src/arcmira/products/related/types/products_related_request_is_appearance.py create mode 100644 src/arcmira/products/related/types/products_related_request_mode.py create mode 100644 src/arcmira/products/related/types/products_related_request_order.py create mode 100644 src/arcmira/products/related/types/topics_related_request_is_appearance.py create mode 100644 src/arcmira/products/related/types/topics_related_request_mode.py create mode 100644 src/arcmira/products/related/types/topics_related_request_order.py create mode 100644 src/arcmira/py.typed create mode 100644 src/arcmira/raw_client.py create mode 100644 src/arcmira/recommendations/__init__.py create mode 100644 src/arcmira/recommendations/client.py create mode 100644 src/arcmira/recommendations/raw_client.py create mode 100644 src/arcmira/recommendations/types/__init__.py create mode 100644 src/arcmira/recommendations/types/list_recommendations_request_entity_type.py create mode 100644 src/arcmira/recommendations/types/list_recommendations_request_mention_class.py create mode 100644 src/arcmira/recommendations/types/list_recommendations_request_src.py create mode 100644 src/arcmira/team/__init__.py create mode 100644 src/arcmira/team/client.py create mode 100644 src/arcmira/team/raw_client.py create mode 100644 src/arcmira/team/usage_events/__init__.py create mode 100644 src/arcmira/team/usage_events/client.py create mode 100644 src/arcmira/team/usage_events/raw_client.py create mode 100644 src/arcmira/topics/__init__.py create mode 100644 src/arcmira/topics/client.py create mode 100644 src/arcmira/topics/raw_client.py create mode 100644 src/arcmira/topics/related/__init__.py create mode 100644 src/arcmira/topics/related/client.py create mode 100644 src/arcmira/topics/related/raw_client.py create mode 100644 src/arcmira/topics/related/types/__init__.py create mode 100644 src/arcmira/topics/related/types/channels_related_request_is_appearance.py create mode 100644 src/arcmira/topics/related/types/channels_related_request_mode.py create mode 100644 src/arcmira/topics/related/types/channels_related_request_order.py create mode 100644 src/arcmira/topics/related/types/organizations_related_request_is_appearance.py create mode 100644 src/arcmira/topics/related/types/organizations_related_request_mode.py create mode 100644 src/arcmira/topics/related/types/organizations_related_request_order.py create mode 100644 src/arcmira/topics/related/types/people_related_request_is_appearance.py create mode 100644 src/arcmira/topics/related/types/people_related_request_mode.py create mode 100644 src/arcmira/topics/related/types/people_related_request_order.py create mode 100644 src/arcmira/topics/related/types/products_related_request_is_appearance.py create mode 100644 src/arcmira/topics/related/types/products_related_request_mode.py create mode 100644 src/arcmira/topics/related/types/products_related_request_order.py create mode 100644 src/arcmira/topics/related/types/topics_related_request_is_appearance.py create mode 100644 src/arcmira/topics/related/types/topics_related_request_mode.py create mode 100644 src/arcmira/topics/related/types/topics_related_request_order.py create mode 100644 src/arcmira/trackers/__init__.py create mode 100644 src/arcmira/trackers/alerts/__init__.py create mode 100644 src/arcmira/trackers/alerts/client.py create mode 100644 src/arcmira/trackers/alerts/raw_client.py create mode 100644 src/arcmira/trackers/client.py create mode 100644 src/arcmira/trackers/raw_client.py create mode 100644 src/arcmira/trackers/types/__init__.py create mode 100644 src/arcmira/trackers/types/create_trackers_request_entity_type.py create mode 100644 src/arcmira/trackers/types/create_trackers_request_person_match_mode.py create mode 100644 src/arcmira/trackers/types/update_trackers_request_person_match_mode.py create mode 100644 src/arcmira/transcripts/__init__.py create mode 100644 src/arcmira/transcripts/client.py create mode 100644 src/arcmira/transcripts/edits/__init__.py create mode 100644 src/arcmira/transcripts/edits/client.py create mode 100644 src/arcmira/transcripts/edits/raw_client.py create mode 100644 src/arcmira/transcripts/merges/__init__.py create mode 100644 src/arcmira/transcripts/merges/client.py create mode 100644 src/arcmira/transcripts/merges/raw_client.py create mode 100644 src/arcmira/transcripts/raw_client.py create mode 100644 src/arcmira/transcripts/speakers/__init__.py create mode 100644 src/arcmira/transcripts/speakers/client.py create mode 100644 src/arcmira/transcripts/speakers/raw_client.py create mode 100644 src/arcmira/transcripts/types/__init__.py create mode 100644 src/arcmira/transcripts/types/captions_transcripts_request_src.py create mode 100644 src/arcmira/transcripts/types/get_transcripts_request_quality.py create mode 100644 src/arcmira/transcripts/types/get_transcripts_request_src.py create mode 100644 src/arcmira/transcripts/types/list_requests_transcripts_request_src.py create mode 100644 src/arcmira/transcripts/types/search_transcripts_request_source.py create mode 100644 src/arcmira/transcripts/types/search_transcripts_request_src.py create mode 100644 src/arcmira/transcripts/types/status_transcripts_request_src.py create mode 100644 src/arcmira/types/__init__.py create mode 100644 src/arcmira/types/account_settings.py create mode 100644 src/arcmira/types/alert.py create mode 100644 src/arcmira/types/alert_evidence_kind.py create mode 100644 src/arcmira/types/alert_list_response.py create mode 100644 src/arcmira/types/alert_monitor.py create mode 100644 src/arcmira/types/alert_tracker.py create mode 100644 src/arcmira/types/bad_ranking_change.py create mode 100644 src/arcmira/types/caption_track.py create mode 100644 src/arcmira/types/channel_coverage_response.py create mode 100644 src/arcmira/types/channel_coverage_response_channel.py create mode 100644 src/arcmira/types/channel_coverage_response_channel_source_mix.py create mode 100644 src/arcmira/types/channel_guest_list_response.py create mode 100644 src/arcmira/types/channel_guest_list_response_export_capabilities.py create mode 100644 src/arcmira/types/channel_guest_list_response_items_item.py create mode 100644 src/arcmira/types/channel_guest_list_response_items_item_sentiment.py create mode 100644 src/arcmira/types/channel_page_response.py create mode 100644 src/arcmira/types/channel_page_response_channel_info.py create mode 100644 src/arcmira/types/channel_page_response_entity.py create mode 100644 src/arcmira/types/channel_page_response_entity_owner.py create mode 100644 src/arcmira/types/channel_page_response_entity_type.py create mode 100644 src/arcmira/types/channel_page_response_episodes_by_month_item.py create mode 100644 src/arcmira/types/channel_page_response_episodes_item.py create mode 100644 src/arcmira/types/channel_page_response_episodes_item_platform.py create mode 100644 src/arcmira/types/channel_page_response_episodes_item_sentiment.py create mode 100644 src/arcmira/types/channel_page_response_episodes_item_timestamp.py create mode 100644 src/arcmira/types/channel_page_response_episodes_item_type.py create mode 100644 src/arcmira/types/channel_page_response_guests_item.py create mode 100644 src/arcmira/types/channel_page_response_guests_item_role.py create mode 100644 src/arcmira/types/channel_page_response_guests_item_sentiment.py create mode 100644 src/arcmira/types/channel_page_response_hosts_detailed_item.py create mode 100644 src/arcmira/types/channel_page_response_hosts_detailed_item_sentiment.py create mode 100644 src/arcmira/types/channel_page_response_organizations_item.py create mode 100644 src/arcmira/types/channel_page_response_organizations_item_sentiment.py create mode 100644 src/arcmira/types/channel_page_response_products_item.py create mode 100644 src/arcmira/types/channel_page_response_products_item_sentiment.py create mode 100644 src/arcmira/types/channel_page_response_recommendations_summary.py create mode 100644 src/arcmira/types/channel_page_response_stats.py create mode 100644 src/arcmira/types/channel_page_response_topics_item.py create mode 100644 src/arcmira/types/channel_page_response_topics_item_sentiment.py create mode 100644 src/arcmira/types/channel_sponsor.py create mode 100644 src/arcmira/types/channel_sponsor_entity.py create mode 100644 src/arcmira/types/channel_sponsor_sponsor_status.py create mode 100644 src/arcmira/types/channel_sponsors_response.py create mode 100644 src/arcmira/types/channel_sponsors_response_access.py create mode 100644 src/arcmira/types/channel_sponsors_response_access_gate.py create mode 100644 src/arcmira/types/channel_sponsors_response_access_reason.py create mode 100644 src/arcmira/types/channel_sponsors_response_access_type.py create mode 100644 src/arcmira/types/channel_sponsors_response_access_unlock.py create mode 100644 src/arcmira/types/channel_sponsors_response_access_unlock_action.py create mode 100644 src/arcmira/types/channel_sponsors_response_channel.py create mode 100644 src/arcmira/types/channel_sponsors_response_meta.py create mode 100644 src/arcmira/types/channel_videos_response.py create mode 100644 src/arcmira/types/channel_videos_response_channel.py create mode 100644 src/arcmira/types/channel_videos_response_episodes_item.py create mode 100644 src/arcmira/types/correction_accepted_response.py create mode 100644 src/arcmira/types/correction_accepted_response_kind.py create mode 100644 src/arcmira/types/correction_seq_mismatch_response.py create mode 100644 src/arcmira/types/delivery_issue_change.py create mode 100644 src/arcmira/types/delivery_issue_change_channel.py create mode 100644 src/arcmira/types/entity.py create mode 100644 src/arcmira/types/entity_card.py create mode 100644 src/arcmira/types/entity_cards_response.py create mode 100644 src/arcmira/types/entity_channel_list_response.py create mode 100644 src/arcmira/types/entity_channel_list_response_export_capabilities.py create mode 100644 src/arcmira/types/entity_channel_list_response_items_item.py create mode 100644 src/arcmira/types/entity_detail_recommendations_summary.py create mode 100644 src/arcmira/types/entity_detail_response.py create mode 100644 src/arcmira/types/entity_lookup_response.py create mode 100644 src/arcmira/types/entity_momentum_response.py create mode 100644 src/arcmira/types/entity_momentum_response_access.py create mode 100644 src/arcmira/types/entity_momentum_response_access_gate.py create mode 100644 src/arcmira/types/entity_momentum_response_access_reason.py create mode 100644 src/arcmira/types/entity_momentum_response_access_type.py create mode 100644 src/arcmira/types/entity_momentum_response_access_unlock.py create mode 100644 src/arcmira/types/entity_momentum_response_access_unlock_action.py create mode 100644 src/arcmira/types/entity_momentum_response_paid_vs_organic.py create mode 100644 src/arcmira/types/entity_momentum_response_top_shows_item.py create mode 100644 src/arcmira/types/entity_momentum_response_verdict.py create mode 100644 src/arcmira/types/entity_momentum_response_volume.py create mode 100644 src/arcmira/types/entity_organization_list_response.py create mode 100644 src/arcmira/types/entity_organization_list_response_export_capabilities.py create mode 100644 src/arcmira/types/entity_organization_list_response_items_item.py create mode 100644 src/arcmira/types/entity_organization_list_response_items_item_sentiment.py create mode 100644 src/arcmira/types/entity_page_mention.py create mode 100644 src/arcmira/types/entity_page_mention_excerpt.py create mode 100644 src/arcmira/types/entity_page_mention_excerpt_public_source_class.py create mode 100644 src/arcmira/types/entity_page_mention_platform.py create mode 100644 src/arcmira/types/entity_page_mention_sentiment.py create mode 100644 src/arcmira/types/entity_page_mention_type.py create mode 100644 src/arcmira/types/entity_people_list_response.py create mode 100644 src/arcmira/types/entity_people_list_response_export_capabilities.py create mode 100644 src/arcmira/types/entity_people_list_response_items_item.py create mode 100644 src/arcmira/types/entity_people_list_response_items_item_sentiment.py create mode 100644 src/arcmira/types/entity_people_list_response_people_mode.py create mode 100644 src/arcmira/types/entity_product_list_response.py create mode 100644 src/arcmira/types/entity_product_list_response_export_capabilities.py create mode 100644 src/arcmira/types/entity_product_list_response_items_item.py create mode 100644 src/arcmira/types/entity_product_list_response_items_item_sentiment.py create mode 100644 src/arcmira/types/entity_ref.py create mode 100644 src/arcmira/types/entity_resolve_response.py create mode 100644 src/arcmira/types/entity_resolve_response_ask.py create mode 100644 src/arcmira/types/entity_resolve_response_ask_options_item.py create mode 100644 src/arcmira/types/entity_resolve_response_confidence.py create mode 100644 src/arcmira/types/entity_search_response.py create mode 100644 src/arcmira/types/entity_search_result.py create mode 100644 src/arcmira/types/entity_search_result_recommendations_summary.py create mode 100644 src/arcmira/types/entity_topic_list_response.py create mode 100644 src/arcmira/types/entity_topic_list_response_export_capabilities.py create mode 100644 src/arcmira/types/entity_topic_list_response_items_item.py create mode 100644 src/arcmira/types/entity_topic_list_response_items_item_sentiment.py create mode 100644 src/arcmira/types/error.py create mode 100644 src/arcmira/types/error_error.py create mode 100644 src/arcmira/types/error_error_gate.py create mode 100644 src/arcmira/types/error_error_reason.py create mode 100644 src/arcmira/types/error_error_type.py create mode 100644 src/arcmira/types/error_error_unlock.py create mode 100644 src/arcmira/types/error_error_unlock_action.py create mode 100644 src/arcmira/types/exposure_meta.py create mode 100644 src/arcmira/types/exposure_meta_access.py create mode 100644 src/arcmira/types/exposure_meta_access_chart.py create mode 100644 src/arcmira/types/exposure_meta_access_cls.py create mode 100644 src/arcmira/types/exposure_meta_access_freshness.py create mode 100644 src/arcmira/types/exposure_meta_access_ladder.py create mode 100644 src/arcmira/types/exposure_meta_access_rows.py create mode 100644 src/arcmira/types/exposure_meta_access_rows_entities.py create mode 100644 src/arcmira/types/exposure_meta_access_rows_media.py create mode 100644 src/arcmira/types/exposure_meta_access_rows_topics.py create mode 100644 src/arcmira/types/exposure_meta_access_unlock.py create mode 100644 src/arcmira/types/exposure_meta_access_unlock_limit_action.py create mode 100644 src/arcmira/types/exposure_meta_access_unlock_src.py create mode 100644 src/arcmira/types/exposure_meta_access_view.py create mode 100644 src/arcmira/types/exposure_meta_access_withheld_item.py create mode 100644 src/arcmira/types/exposure_meta_access_withheld_item_kind.py create mode 100644 src/arcmira/types/exposure_meta_access_withheld_item_param.py create mode 100644 src/arcmira/types/exposure_meta_access_withheld_item_section.py create mode 100644 src/arcmira/types/exposure_meta_access_withheld_item_what.py create mode 100644 src/arcmira/types/exposure_meta_credits.py create mode 100644 src/arcmira/types/exposure_meta_credits_on_demand.py create mode 100644 src/arcmira/types/exposure_meta_credits_plan.py create mode 100644 src/arcmira/types/exposure_meta_free_limit.py create mode 100644 src/arcmira/types/exposure_meta_limit_action.py create mode 100644 src/arcmira/types/exposure_meta_limits.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_experiment.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_mentions.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_mentions_experiment.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_mentions_subject.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_mentions_teaser_items_item.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_subject.py create mode 100644 src/arcmira/types/exposure_meta_recent_preview_teaser_items_item.py create mode 100644 src/arcmira/types/exposure_meta_totals.py create mode 100644 src/arcmira/types/exposure_meta_usage_limit_type.py create mode 100644 src/arcmira/types/feedback_correction_result.py create mode 100644 src/arcmira/types/feedback_correction_result_recommendation.py create mode 100644 src/arcmira/types/feedback_correction_result_recommendation_media.py create mode 100644 src/arcmira/types/feedback_correction_result_recommendation_media_source_channel.py create mode 100644 src/arcmira/types/feedback_correction_result_status.py create mode 100644 src/arcmira/types/feedback_readback_correction.py create mode 100644 src/arcmira/types/feedback_readback_correction_status.py create mode 100644 src/arcmira/types/feedback_readback_response.py create mode 100644 src/arcmira/types/feedback_readback_response_status.py create mode 100644 src/arcmira/types/feedback_response.py create mode 100644 src/arcmira/types/freeform_suggested_change.py create mode 100644 src/arcmira/types/health_response.py create mode 100644 src/arcmira/types/health_response_status.py create mode 100644 src/arcmira/types/health_response_version.py create mode 100644 src/arcmira/types/me_response.py create mode 100644 src/arcmira/types/me_response_credential_kind.py create mode 100644 src/arcmira/types/me_response_usage.py create mode 100644 src/arcmira/types/me_response_usage_credits.py create mode 100644 src/arcmira/types/me_response_usage_credits_on_demand.py create mode 100644 src/arcmira/types/me_response_usage_credits_plan.py create mode 100644 src/arcmira/types/me_response_usage_hits.py create mode 100644 src/arcmira/types/me_settings_response.py create mode 100644 src/arcmira/types/mention.py create mode 100644 src/arcmira/types/mention_counts_response.py create mode 100644 src/arcmira/types/mention_counts_response_mode.py create mode 100644 src/arcmira/types/mention_counts_response_rows_item.py create mode 100644 src/arcmira/types/mention_counts_response_shared_item.py create mode 100644 src/arcmira/types/mention_counts_response_shared_item_by_channel_item.py create mode 100644 src/arcmira/types/mention_list_response.py create mode 100644 src/arcmira/types/mention_list_response_entity.py create mode 100644 src/arcmira/types/mention_list_response_unlock.py create mode 100644 src/arcmira/types/mention_media.py create mode 100644 src/arcmira/types/mention_media_source_channel.py create mode 100644 src/arcmira/types/mention_recommendations.py create mode 100644 src/arcmira/types/mention_sentiment.py create mode 100644 src/arcmira/types/merge_suggestion_change.py create mode 100644 src/arcmira/types/message_response.py create mode 100644 src/arcmira/types/missed_alert_change.py create mode 100644 src/arcmira/types/missing_result_change.py create mode 100644 src/arcmira/types/monitor.py create mode 100644 src/arcmira/types/monitor_add_trackers_response.py create mode 100644 src/arcmira/types/monitor_delete_response.py create mode 100644 src/arcmira/types/monitor_email_recipients_item.py create mode 100644 src/arcmira/types/monitor_email_recipients_item_invitation_status.py create mode 100644 src/arcmira/types/monitor_email_recipients_item_status.py create mode 100644 src/arcmira/types/monitor_list_response.py create mode 100644 src/arcmira/types/monitor_list_response_monitors_item.py create mode 100644 src/arcmira/types/monitor_list_response_monitors_item_slack_integration.py create mode 100644 src/arcmira/types/monitor_mutation_response.py create mode 100644 src/arcmira/types/monitor_mutation_response_monitor.py create mode 100644 src/arcmira/types/monitor_trackers_response.py create mode 100644 src/arcmira/types/monitor_trackers_response_trackers_item.py create mode 100644 src/arcmira/types/named_entity_ref.py create mode 100644 src/arcmira/types/open_api_document.py create mode 100644 src/arcmira/types/open_api_document_info.py create mode 100644 src/arcmira/types/open_api_document_info_contact.py create mode 100644 src/arcmira/types/open_api_document_servers_item.py create mode 100644 src/arcmira/types/organization_page_response.py create mode 100644 src/arcmira/types/organization_page_response_channels_item.py create mode 100644 src/arcmira/types/organization_page_response_channels_item_sentiment.py create mode 100644 src/arcmira/types/organization_page_response_entity.py create mode 100644 src/arcmira/types/organization_page_response_entity_owned_channels_item.py create mode 100644 src/arcmira/types/organization_page_response_entity_owned_products_item.py create mode 100644 src/arcmira/types/organization_page_response_entity_type.py create mode 100644 src/arcmira/types/organization_page_response_mentions_by_month_item.py create mode 100644 src/arcmira/types/organization_page_response_people_item.py create mode 100644 src/arcmira/types/organization_page_response_people_item_sentiment.py create mode 100644 src/arcmira/types/organization_page_response_products_item.py create mode 100644 src/arcmira/types/organization_page_response_products_item_sentiment.py create mode 100644 src/arcmira/types/organization_page_response_role_edge.py create mode 100644 src/arcmira/types/organization_page_response_role_edge_label.py create mode 100644 src/arcmira/types/organization_page_response_role_edge_people_item.py create mode 100644 src/arcmira/types/organization_page_response_role_edge_recent_appearances_item.py create mode 100644 src/arcmira/types/organization_page_response_role_edge_role.py create mode 100644 src/arcmira/types/organization_page_response_stats.py create mode 100644 src/arcmira/types/organization_page_response_topics_item.py create mode 100644 src/arcmira/types/organization_page_response_topics_item_sentiment.py create mode 100644 src/arcmira/types/person_appearance_list_response.py create mode 100644 src/arcmira/types/person_appearance_list_response_items_item.py create mode 100644 src/arcmira/types/person_appearance_list_response_items_item_platform.py create mode 100644 src/arcmira/types/person_appearance_list_response_items_item_sentiment.py create mode 100644 src/arcmira/types/person_appearance_list_response_items_item_type.py create mode 100644 src/arcmira/types/person_page_response.py create mode 100644 src/arcmira/types/person_page_response_appearances_by_month_item.py create mode 100644 src/arcmira/types/person_page_response_appearances_item.py create mode 100644 src/arcmira/types/person_page_response_appearances_item_platform.py create mode 100644 src/arcmira/types/person_page_response_appearances_item_sentiment.py create mode 100644 src/arcmira/types/person_page_response_appearances_item_type.py create mode 100644 src/arcmira/types/person_page_response_brands_item.py create mode 100644 src/arcmira/types/person_page_response_brands_item_sentiment.py create mode 100644 src/arcmira/types/person_page_response_entity.py create mode 100644 src/arcmira/types/person_page_response_entity_owned_channels_item.py create mode 100644 src/arcmira/types/person_page_response_entity_owned_products_item.py create mode 100644 src/arcmira/types/person_page_response_mentions_by_month_item.py create mode 100644 src/arcmira/types/person_page_response_mentions_item.py create mode 100644 src/arcmira/types/person_page_response_mentions_item_platform.py create mode 100644 src/arcmira/types/person_page_response_mentions_item_sentiment.py create mode 100644 src/arcmira/types/person_page_response_mentions_item_type.py create mode 100644 src/arcmira/types/person_page_response_people_item.py create mode 100644 src/arcmira/types/person_page_response_people_item_role.py create mode 100644 src/arcmira/types/person_page_response_people_item_sentiment.py create mode 100644 src/arcmira/types/person_page_response_products_item.py create mode 100644 src/arcmira/types/person_page_response_products_item_sentiment.py create mode 100644 src/arcmira/types/person_page_response_role_edge.py create mode 100644 src/arcmira/types/person_page_response_role_edge_label.py create mode 100644 src/arcmira/types/person_page_response_role_edge_role.py create mode 100644 src/arcmira/types/person_page_response_stats.py create mode 100644 src/arcmira/types/person_page_response_topics_item.py create mode 100644 src/arcmira/types/person_page_response_topics_item_sentiment.py create mode 100644 src/arcmira/types/product_page_response.py create mode 100644 src/arcmira/types/product_page_response_channels_item.py create mode 100644 src/arcmira/types/product_page_response_channels_item_sentiment.py create mode 100644 src/arcmira/types/product_page_response_entity.py create mode 100644 src/arcmira/types/product_page_response_entity_owner.py create mode 100644 src/arcmira/types/product_page_response_entity_parent_org.py create mode 100644 src/arcmira/types/product_page_response_entity_type.py create mode 100644 src/arcmira/types/product_page_response_mentions_by_month_item.py create mode 100644 src/arcmira/types/product_page_response_opportunities.py create mode 100644 src/arcmira/types/product_page_response_organizations_item.py create mode 100644 src/arcmira/types/product_page_response_organizations_item_sentiment.py create mode 100644 src/arcmira/types/product_page_response_people_item.py create mode 100644 src/arcmira/types/product_page_response_people_item_sentiment.py create mode 100644 src/arcmira/types/product_page_response_stats.py create mode 100644 src/arcmira/types/product_page_response_topics_item.py create mode 100644 src/arcmira/types/product_page_response_topics_item_sentiment.py create mode 100644 src/arcmira/types/published_excerpt.py create mode 100644 src/arcmira/types/published_excerpt_public_source_class.py create mode 100644 src/arcmira/types/recommendation.py create mode 100644 src/arcmira/types/recommendation_enrichment_item.py create mode 100644 src/arcmira/types/recommendation_list_response.py create mode 100644 src/arcmira/types/recommendation_list_response_entity.py create mode 100644 src/arcmira/types/recommendation_media.py create mode 100644 src/arcmira/types/recommendation_media_source_channel.py create mode 100644 src/arcmira/types/resolve_candidate.py create mode 100644 src/arcmira/types/resolve_candidate_match.py create mode 100644 src/arcmira/types/resolve_suggestion.py create mode 100644 src/arcmira/types/resolve_suggestion_match.py create mode 100644 src/arcmira/types/resolve_suggestion_reason.py create mode 100644 src/arcmira/types/search_request_type.py create mode 100644 src/arcmira/types/search_resolve_response.py create mode 100644 src/arcmira/types/search_resolve_response_entity.py create mode 100644 src/arcmira/types/signup_sent_response.py create mode 100644 src/arcmira/types/signup_sent_response_next.py create mode 100644 src/arcmira/types/signup_sent_response_next_method.py create mode 100644 src/arcmira/types/signup_verified_response.py create mode 100644 src/arcmira/types/speaker_identification_submitted_response.py create mode 100644 src/arcmira/types/speaker_identification_submitted_response_identification.py create mode 100644 src/arcmira/types/speaker_identification_submitted_response_identification_entity.py create mode 100644 src/arcmira/types/speaker_identification_submitted_response_identification_status.py create mode 100644 src/arcmira/types/stale_metadata_change.py create mode 100644 src/arcmira/types/team_member.py create mode 100644 src/arcmira/types/team_member_role.py create mode 100644 src/arcmira/types/team_member_seat_type.py create mode 100644 src/arcmira/types/team_member_spend.py create mode 100644 src/arcmira/types/team_member_spend_role.py create mode 100644 src/arcmira/types/team_member_spend_seat_type.py create mode 100644 src/arcmira/types/team_members_response.py create mode 100644 src/arcmira/types/team_members_response_team.py create mode 100644 src/arcmira/types/team_spend_response.py create mode 100644 src/arcmira/types/team_usage_event.py create mode 100644 src/arcmira/types/team_usage_events_response.py create mode 100644 src/arcmira/types/topic_page_response.py create mode 100644 src/arcmira/types/topic_page_response_channels_item.py create mode 100644 src/arcmira/types/topic_page_response_channels_item_sentiment.py create mode 100644 src/arcmira/types/topic_page_response_companies_item.py create mode 100644 src/arcmira/types/topic_page_response_companies_item_sentiment.py create mode 100644 src/arcmira/types/topic_page_response_entity.py create mode 100644 src/arcmira/types/topic_page_response_entity_type.py create mode 100644 src/arcmira/types/topic_page_response_mentions_by_month_item.py create mode 100644 src/arcmira/types/topic_page_response_products_item.py create mode 100644 src/arcmira/types/topic_page_response_products_item_sentiment.py create mode 100644 src/arcmira/types/topic_page_response_related_topics_item.py create mode 100644 src/arcmira/types/topic_page_response_related_topics_item_sentiment.py create mode 100644 src/arcmira/types/topic_page_response_stats.py create mode 100644 src/arcmira/types/topic_page_response_voices_item.py create mode 100644 src/arcmira/types/topic_page_response_voices_item_role.py create mode 100644 src/arcmira/types/topic_page_response_voices_item_sentiment.py create mode 100644 src/arcmira/types/tracker.py create mode 100644 src/arcmira/types/tracker_list_response.py create mode 100644 src/arcmira/types/tracker_mutation_response.py create mode 100644 src/arcmira/types/transcript_edit_submitted_response.py create mode 100644 src/arcmira/types/transcript_edit_submitted_response_edit.py create mode 100644 src/arcmira/types/transcript_edit_submitted_response_edit_status.py create mode 100644 src/arcmira/types/transcript_pending.py create mode 100644 src/arcmira/types/transcript_pending_premium_job.py create mode 100644 src/arcmira/types/transcript_pending_quality.py create mode 100644 src/arcmira/types/transcript_purchase_quote.py create mode 100644 src/arcmira/types/transcript_purchase_quote_billing_scope.py create mode 100644 src/arcmira/types/transcript_purchase_quote_charge.py create mode 100644 src/arcmira/types/transcript_purchase_quote_charge_unit.py create mode 100644 src/arcmira/types/transcript_quote.py create mode 100644 src/arcmira/types/transcript_response.py create mode 100644 src/arcmira/types/transcript_response_access.py create mode 100644 src/arcmira/types/transcript_response_access_gate.py create mode 100644 src/arcmira/types/transcript_response_access_reason.py create mode 100644 src/arcmira/types/transcript_response_access_type.py create mode 100644 src/arcmira/types/transcript_response_access_unlock.py create mode 100644 src/arcmira/types/transcript_response_access_unlock_action.py create mode 100644 src/arcmira/types/transcript_response_lines_item.py create mode 100644 src/arcmira/types/transcript_response_paragraphs_item.py create mode 100644 src/arcmira/types/transcript_response_premium_job.py create mode 100644 src/arcmira/types/transcript_response_quality.py create mode 100644 src/arcmira/types/transcript_response_range.py create mode 100644 src/arcmira/types/transcript_response_source.py create mode 100644 src/arcmira/types/transcript_response_speakers_item.py create mode 100644 src/arcmira/types/transcript_result.py create mode 100644 src/arcmira/types/transcript_search_chunk.py create mode 100644 src/arcmira/types/transcript_search_response.py create mode 100644 src/arcmira/types/transcript_search_response_access.py create mode 100644 src/arcmira/types/transcript_search_response_access_gate.py create mode 100644 src/arcmira/types/transcript_search_response_access_reason.py create mode 100644 src/arcmira/types/transcript_search_response_access_type.py create mode 100644 src/arcmira/types/transcript_search_response_access_unlock.py create mode 100644 src/arcmira/types/transcript_search_response_access_unlock_action.py create mode 100644 src/arcmira/types/transcript_search_response_filters.py create mode 100644 src/arcmira/types/transcript_search_response_search_index.py create mode 100644 src/arcmira/types/transcript_search_response_search_index_state.py create mode 100644 src/arcmira/types/transcript_settings.py create mode 100644 src/arcmira/types/transcript_settings_quality.py create mode 100644 src/arcmira/types/transcript_video.py create mode 100644 src/arcmira/types/transcription_list_response.py create mode 100644 src/arcmira/types/transcription_list_response_requests_item.py create mode 100644 src/arcmira/types/transcription_request.py create mode 100644 src/arcmira/types/transcription_request_charge.py create mode 100644 src/arcmira/types/transcription_request_charge_unit.py create mode 100644 src/arcmira/types/transcription_request_quote.py create mode 100644 src/arcmira/types/transcription_request_stage.py create mode 100644 src/arcmira/types/transcription_request_state.py create mode 100644 src/arcmira/types/transcription_request_status.py create mode 100644 src/arcmira/types/transcription_submit_response.py create mode 100644 src/arcmira/types/video_captions_response.py create mode 100644 src/arcmira/types/video_merge_list_response.py create mode 100644 src/arcmira/types/video_merge_list_response_merges_item.py create mode 100644 src/arcmira/types/video_merge_list_response_merges_item_status.py create mode 100644 src/arcmira/types/video_merge_submitted_response.py create mode 100644 src/arcmira/types/video_merge_submitted_response_merge.py create mode 100644 src/arcmira/types/video_merge_submitted_response_merge_status.py create mode 100644 src/arcmira/types/webhook_secret_rotate_response.py create mode 100644 src/arcmira/types/withdrawn_response.py create mode 100644 src/arcmira/types/wrong_classification_change.py create mode 100644 src/arcmira/types/wrong_classification_change_mention_class.py create mode 100644 src/arcmira/types/wrong_entity_change.py create mode 100644 src/arcmira/types/wrong_entity_type_change.py create mode 100644 src/arcmira/types/wrong_entity_type_change_field.py create mode 100644 tests/fixtures/transcription-responses.json create mode 100644 tests/test_generation.py create mode 100644 tests/test_transcription.py create mode 100644 uv.lock diff --git a/.github/allowed_signers b/.github/allowed_signers new file mode 100644 index 0000000..f6acde3 --- /dev/null +++ b/.github/allowed_signers @@ -0,0 +1 @@ +zealous1@users.noreply.github.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKhErQc7tT+Kldflqb2usK8py/h6vS9jwlk6rldjFT+v diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e53219a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI +on: + push: + branches: [master] + pull_request: +permissions: + contents: read +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - run: bash scripts/check-commit-identity.sh + - uses: astral-sh/setup-uv@v6 + - run: uv sync --python 3.12 + - run: uv run python -m unittest discover -s tests -v + - run: uv build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7c5d49b..424f0e0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: - name: Publish if this version is new run: | - version="$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/arcmira/__init__.py)" + version="$(cat VERSION)" if curl -sfI "https://pypi.org/pypi/arcmira/${version}/json" >/dev/null; then echo "arcmira==${version} is already on the registry. Skipping publish." exit 0 diff --git a/.gitignore b/.gitignore index 9968b53..b3afb7a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ dist/ __pycache__/ .venv/ .python-version +.generated/ +.fern/ +fern/openapi.sdk.json diff --git a/README.md b/README.md index c1ec285..251b512 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,83 @@ -# Arcmira +# Arcmira Python SDK -Arcmira is an SF-based AI company and the search engine for the spoken web. +The official Python client for the [Arcmira API](https://arcmira.com/docs), with synchronous and asynchronous clients, typed responses, and cursor pagination. -This package is the official PyPI name for Arcmira. It exports public URLs. It is not an SDK. +```sh +pip install arcmira +``` + +Set `ARCMIRA_API_KEY` or pass `api_key` to the client. + +```python +from arcmira import Arcmira + +client = Arcmira() +quote = client.transcripts.quote(video_id="dQw4w9WgXcQ") +print(quote.quote.rows, quote.charge) +``` + +A quote is free. Preparation purchases the whole video, even if you later read a short window. Persist the intent before submitting it. Choose ceilings after reviewing the quote and authorizing the cost. + +```python +intent = dict( + video_id="dQw4w9WgXcQ", + max_rows=300, + max_on_demand_cents=0, + idempotency_key="saved-order-dQw4w9WgXcQ-1", +) +order = client.transcripts.with_raw_response.request(**intent) +print(order.status_code, order.data.request.state) +``` + +`idempotency_key` and `max_rows` are required. `max_on_demand_cents` defaults to zero. If the response is lost, retry with the same saved key and exact input. The response retains `{request, existing?}` and the `Idempotency-Replayed` header. A changed intent with the same key returns `409 idempotency_conflict`. + +GET never purchases Premium. A ready read includes transcript lines. A pending read includes a status URL and polling delay. + +```python +result = client.transcripts.with_raw_response.get( + video_id="dQw4w9WgXcQ", quality="premium" +) +if result.data.state == "ready": + print(result.data.lines) +else: + print(result.data.status_url, result.data.next_poll_seconds) +print(result.status_code, result.headers) +``` + +A refusal raises a typed error derived from `arcmira.core.api_error.ApiError`. Its `body` retains the public error, quote, and recovery URLs. `refund_pending` is unfinished; a refund is complete only when the request reports `refunded`. ```python -import arcmira -# arcmira.api_base == "https://api.arcmira.com/v1" +for request in client.transcripts.list_requests(limit=10): + print(request.id, request.state) +for episode in client.channels.videos.list(channel_id="UC-DRzaGnL_vtBUpCFH5M0tg", limit=10): + print(episode.video_id) ``` -- Product: https://arcmira.com -- Docs: https://arcmira.com/docs -- HTTP API: https://api.arcmira.com/v1 -- Agent index: https://arcmira.com/llms.txt (this repo also has `llms.txt`, which points there) -- Source: https://github.com/arcmira/python +Pagination follows the actual `requests` and `episodes` arrays. Cursors remain opaque and filters stay the same between pages. + +For asynchronous calls, use `AsyncArcmira` and await the same methods. Paginated methods return async iterators after awaiting the initial page. + +```python +from arcmira import AsyncArcmira + +async def history(): + client = AsyncArcmira() + async for request in await client.transcripts.list_requests(limit=10): + print(request.id) +``` + +See the [generated reference](reference.md) for all endpoints. + +## Regenerate and verify + +Run `bash scripts/generate.sh` with Node 22 or newer, Python 3, Docker, and Fern access for the `arcmira` organization. Generation pins Fern CLI 5.131.1 and Python generator 5.31.0, disables CLI version redirection and telemetry, and reads `fern/openapi.json`. The overlay combines distinct success schemas and rejects unknown or ambiguous cursor collections. Generated source is never edited by hand. + +```sh +uv sync +uv run python -m unittest discover -s tests -v +uv build +``` -The official MCP server is [arcmira/mcp](https://github.com/arcmira/mcp). What it does: https://arcmira.com/mcp. Setup for each host: https://arcmira.com/agent-setup. +The tests use a local HTTP server. They check both client variants, state discrimination, response status, quotes, refusals, exact replay input, and opaque pagination. No live API key or purchase is required. -Copyright Arcmira. All rights reserved. See `LICENSE`. +Version 0.3.0 replaces the earlier URL-only placeholder with a usable SDK. The public URL constants remain available. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0d91a54 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0 diff --git a/fern/fern.config.json b/fern/fern.config.json new file mode 100644 index 0000000..61a1936 --- /dev/null +++ b/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "arcmira", + "version": "5.131.1" +} diff --git a/fern/generators.yml b/fern/generators.yml new file mode 100644 index 0000000..f0da3f7 --- /dev/null +++ b/fern/generators.yml @@ -0,0 +1,14 @@ +api: + specs: + - openapi: ./openapi.sdk.json +groups: + python: + generators: + - name: fernapi/fern-python-sdk + version: 5.31.0 + output: + location: local-file-system + path: ../.generated/python + config: + client_class_name: Arcmira + package_name: arcmira diff --git a/fern/method-names.json b/fern/method-names.json new file mode 100644 index 0000000..e791db7 --- /dev/null +++ b/fern/method-names.json @@ -0,0 +1,549 @@ +{ + "get /v1/channels/{}/coverage": { + "group": [ + "channels" + ], + "method": "coverage" + }, + "get /v1/channels/{}": { + "group": [ + "channels" + ], + "method": "get" + }, + "get /v1/channels/{}/guests": { + "group": [ + "channels", + "guests" + ], + "method": "list" + }, + "get /v1/channels/{}/topics": { + "group": [ + "channels", + "related" + ], + "method": "topics" + }, + "get /v1/channels/{}/people": { + "group": [ + "channels", + "related" + ], + "method": "people" + }, + "get /v1/channels/{}/organizations": { + "group": [ + "channels", + "related" + ], + "method": "organizations" + }, + "get /v1/channels/{}/products": { + "group": [ + "channels", + "related" + ], + "method": "products" + }, + "get /v1/channels/{}/channels": { + "group": [ + "channels", + "related" + ], + "method": "channels" + }, + "get /v1/channels/{}/sponsors": { + "group": [ + "channels", + "sponsors" + ], + "method": "list" + }, + "get /v1/channels/{}/videos": { + "group": [ + "channels", + "videos" + ], + "method": "list" + }, + "post /v1/videos/{}/corrections": { + "group": [ + "corrections" + ], + "method": "submit" + }, + "delete /v1/corrections/speaker-edits/{}": { + "group": [ + "corrections" + ], + "method": "withdrawSpeakerEdit" + }, + "delete /v1/corrections/entity-tags/{}": { + "group": [ + "corrections" + ], + "method": "withdrawEntityTag" + }, + "delete /v1/corrections/segment-rewrites/{}": { + "group": [ + "corrections" + ], + "method": "withdrawSegmentRewrite" + }, + "get /v1/entities/search": { + "group": [ + "entities" + ], + "method": "search" + }, + "get /v1/entities/resolve": { + "group": [ + "entities" + ], + "method": "resolve" + }, + "get /v1/entities/lookup": { + "group": [ + "entities" + ], + "method": "lookup" + }, + "get /v1/entities/cards": { + "group": [ + "entities" + ], + "method": "cards" + }, + "get /v1/entities/{}": { + "group": [ + "entities" + ], + "method": "get" + }, + "get /v1/entities/{}/momentum": { + "group": [ + "entities" + ], + "method": "momentum" + }, + "get /v1/entities/{}/mentions": { + "group": [ + "entities", + "mentions" + ], + "method": "list" + }, + "get /v1/entities/{}/recommendations": { + "group": [ + "entities", + "recommendations" + ], + "method": "list" + }, + "post /v1/feedback": { + "group": [ + "feedback" + ], + "method": "submit" + }, + "get /v1/feedback/{}": { + "group": [ + "feedback" + ], + "method": "get" + }, + "get /v1/health": { + "group": [ + "health" + ], + "method": "check" + }, + "get /v1/me": { + "group": [ + "me" + ], + "method": "get" + }, + "patch /v1/me/settings": { + "group": [ + "me" + ], + "method": "updateSettings" + }, + "get /v1/mentions": { + "group": [ + "mentions" + ], + "method": "list" + }, + "get /v1/mentions/counts": { + "group": [ + "mentions" + ], + "method": "count" + }, + "get /v1/monitors": { + "group": [ + "monitors" + ], + "method": "list" + }, + "post /v1/monitors": { + "group": [ + "monitors" + ], + "method": "create" + }, + "delete /v1/monitors/{}": { + "group": [ + "monitors" + ], + "method": "delete" + }, + "patch /v1/monitors/{}": { + "group": [ + "monitors" + ], + "method": "update" + }, + "post /v1/monitors/{}/webhook-secret/rotate": { + "group": [ + "monitors" + ], + "method": "rotateWebhookSecret" + }, + "get /v1/monitors/{}/alerts": { + "group": [ + "monitors", + "alerts" + ], + "method": "list" + }, + "get /v1/monitors/{}/trackers": { + "group": [ + "monitors", + "trackers" + ], + "method": "list" + }, + "post /v1/monitors/{}/trackers": { + "group": [ + "monitors", + "trackers" + ], + "method": "add" + }, + "get /v1/organizations/{}": { + "group": [ + "organizations" + ], + "method": "get" + }, + "get /v1/organizations/{}/topics": { + "group": [ + "organizations", + "related" + ], + "method": "topics" + }, + "get /v1/organizations/{}/people": { + "group": [ + "organizations", + "related" + ], + "method": "people" + }, + "get /v1/organizations/{}/organizations": { + "group": [ + "organizations", + "related" + ], + "method": "organizations" + }, + "get /v1/organizations/{}/products": { + "group": [ + "organizations", + "related" + ], + "method": "products" + }, + "get /v1/organizations/{}/channels": { + "group": [ + "organizations", + "related" + ], + "method": "channels" + }, + "get /v1/people/{}": { + "group": [ + "people" + ], + "method": "get" + }, + "get /v1/people/{}/appearances": { + "group": [ + "people", + "appearances" + ], + "method": "list" + }, + "get /v1/people/{}/topics": { + "group": [ + "people", + "related" + ], + "method": "topics" + }, + "get /v1/people/{}/people": { + "group": [ + "people", + "related" + ], + "method": "people" + }, + "get /v1/people/{}/organizations": { + "group": [ + "people", + "related" + ], + "method": "organizations" + }, + "get /v1/people/{}/products": { + "group": [ + "people", + "related" + ], + "method": "products" + }, + "get /v1/people/{}/channels": { + "group": [ + "people", + "related" + ], + "method": "channels" + }, + "get /v1/products/{}": { + "group": [ + "products" + ], + "method": "get" + }, + "get /v1/products/{}/topics": { + "group": [ + "products", + "related" + ], + "method": "topics" + }, + "get /v1/products/{}/people": { + "group": [ + "products", + "related" + ], + "method": "people" + }, + "get /v1/products/{}/organizations": { + "group": [ + "products", + "related" + ], + "method": "organizations" + }, + "get /v1/products/{}/products": { + "group": [ + "products", + "related" + ], + "method": "products" + }, + "get /v1/products/{}/channels": { + "group": [ + "products", + "related" + ], + "method": "channels" + }, + "get /v1/recommendations": { + "group": [ + "recommendations" + ], + "method": "list" + }, + "get /v1/team/members": { + "group": [ + "team" + ], + "method": "members" + }, + "get /v1/team/spend": { + "group": [ + "team" + ], + "method": "spend" + }, + "get /v1/team/usage-events": { + "group": [ + "team", + "usageEvents" + ], + "method": "list" + }, + "get /v1/topics/{}": { + "group": [ + "topics" + ], + "method": "get" + }, + "get /v1/topics/{}/topics": { + "group": [ + "topics", + "related" + ], + "method": "topics" + }, + "get /v1/topics/{}/people": { + "group": [ + "topics", + "related" + ], + "method": "people" + }, + "get /v1/topics/{}/organizations": { + "group": [ + "topics", + "related" + ], + "method": "organizations" + }, + "get /v1/topics/{}/products": { + "group": [ + "topics", + "related" + ], + "method": "products" + }, + "get /v1/topics/{}/channels": { + "group": [ + "topics", + "related" + ], + "method": "channels" + }, + "get /v1/trackers": { + "group": [ + "trackers" + ], + "method": "list" + }, + "post /v1/trackers": { + "group": [ + "trackers" + ], + "method": "create" + }, + "delete /v1/trackers/{}": { + "group": [ + "trackers" + ], + "method": "delete" + }, + "patch /v1/trackers/{}": { + "group": [ + "trackers" + ], + "method": "update" + }, + "get /v1/trackers/{}/alerts": { + "group": [ + "trackers", + "alerts" + ], + "method": "list" + }, + "get /v1/transcripts/search": { + "group": [ + "transcripts" + ], + "method": "search" + }, + "get /v1/transcripts/{}": { + "group": [ + "transcripts" + ], + "method": "get" + }, + "get /v1/videos/{}/captions": { + "group": [ + "transcripts" + ], + "method": "captions" + }, + "get /v1/transcriptions": { + "group": [ + "transcripts" + ], + "method": "listRequests" + }, + "post /v1/transcriptions": { + "group": [ + "transcripts" + ], + "method": "request" + }, + "get /v1/transcriptions/{}": { + "group": [ + "transcripts" + ], + "method": "status" + }, + "post /v1/transcripts/{}/edits": { + "group": [ + "transcripts", + "edits" + ], + "method": "submit" + }, + "delete /v1/transcripts/{}/edits/{}": { + "group": [ + "transcripts", + "edits" + ], + "method": "withdraw" + }, + "get /v1/transcripts/{}/merges": { + "group": [ + "transcripts", + "merges" + ], + "method": "list" + }, + "post /v1/transcripts/{}/merges": { + "group": [ + "transcripts", + "merges" + ], + "method": "submit" + }, + "delete /v1/transcripts/{}/merges/{}": { + "group": [ + "transcripts", + "merges" + ], + "method": "withdraw" + }, + "post /v1/transcripts/{}/speakers": { + "group": [ + "transcripts", + "speakers" + ], + "method": "identify" + }, + "delete /v1/transcripts/{}/speakers/{}": { + "group": [ + "transcripts", + "speakers" + ], + "method": "withdraw" + } +} diff --git a/fern/openapi.json b/fern/openapi.json new file mode 100644 index 0000000..2c387fc --- /dev/null +++ b/fern/openapi.json @@ -0,0 +1,23649 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Arcmira API", + "version": "1.0.0", + "contact": { + "name": "Arcmira", + "url": "https://arcmira.com" + } + }, + "servers": [ + { + "url": "https://api.arcmira.com" + } + ], + "components": { + "headers": { + "X-Request-Id": { + "description": "The request id, echoed from an X-Request-Id request header or minted as req_. error.request_id carries the same value. Quote it when reporting a problem.", + "schema": { + "type": "string" + } + }, + "X-Arcmira-Version": { + "description": "The API version that answered. Always v1.", + "schema": { + "type": "string", + "enum": [ + "v1" + ] + } + }, + "RateLimit-Limit": { + "description": "Requests allowed per 60-second window for this credential, as GET /v1/me rate_limit reports.", + "schema": { + "type": "integer" + } + }, + "RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "RateLimit-Reset": { + "description": "Unix time in seconds when the current window ends.", + "schema": { + "type": "integer" + } + }, + "Retry-After": { + "description": "Seconds to wait before retrying. error.retry_after_seconds carries the same value on an error.", + "schema": { + "type": "integer" + } + }, + "Idempotency-Replayed": { + "description": "true when this is the stored response to an earlier request with the same Idempotency-Key; the request did not run again.", + "schema": { + "type": "string", + "enum": [ + "true" + ] + } + } + }, + "responses": { + "InvalidRequest": { + "description": "Invalid request", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "AuthenticationError": { + "description": "Authentication error", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "QuotaExceeded": { + "description": "Quota exceeded", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "PermissionError": { + "description": "Permission error", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "NotFound": { + "description": "Not found", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "RateLimited": { + "description": "Rate limit exceeded. Retry-After carries the wait.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "ServerError": { + "description": "Server error", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "arc_sk_*" + } + }, + "schemas": { + "Error": { + "type": "object", + "properties": { + "existingId": { + "type": "string", + "description": "On tracker_already_exists, the existing tracker id. Reuse it instead of creating another tracker." + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error" + ], + "description": "The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling." + }, + "code": { + "type": "string", + "description": "The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first." + }, + "reason": { + "type": "string", + "enum": [ + "no_credential", + "invalid", + "revoked" + ], + "description": "Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable." + }, + "message": { + "type": "string", + "description": "One plain line. Names the fix or the unlock." + }, + "param": { + "type": "string", + "description": "The query or body parameter the gate refused, when one did." + }, + "gate": { + "type": "string", + "enum": [ + "rows", + "key", + "plan", + "freshness", + "exposure_law", + "rate", + "pagination" + ], + "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." + }, + "unlock": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "The plan that lifts the gate." + }, + "url": { + "type": "string", + "description": "Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim." + }, + "offer": { + "type": "null", + "description": "Reserved for the agent-discount offer. Always null today." + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the call does. send_signup_code sends a verification code to an address for an account key." + }, + "method": { + "type": "string", + "description": "HTTP method to use." + }, + "url": { + "type": "string", + "description": "Absolute endpoint carrying its ?src= attribution. Call it verbatim." + } + }, + "required": [ + "kind", + "method", + "url" + ], + "description": "The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens." + } + }, + "required": [ + "tier", + "url", + "offer" + ], + "description": "How to lift the gate. Present when the gate has an unlock." + }, + "retry_after_seconds": { + "type": "integer", + "description": "Present on rate gates. Mirrors the Retry-After header." + }, + "doc_url": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "type", + "code", + "message", + "doc_url", + "request_id" + ] + } + }, + "required": [ + "error" + ], + "x-arcmira-codes": [ + { + "code": "alert_not_found", + "type": "not_found", + "description": "A referenced alert row does not exist or belongs to another account." + }, + { + "code": "api_not_enabled", + "type": "permission_error", + "gate": "plan", + "description": "The plan does not include API access. unlock.url names the plan that does." + }, + { + "code": "appearances_person_only", + "type": "invalid_request_error", + "description": "Appearance filtering applies to person entities only." + }, + { + "code": "channel_not_found", + "type": "not_found", + "description": "No channel matches the id or slug." + }, + { + "code": "email_verification_required", + "type": "permission_error", + "description": "Verify the account email before adding recipients." + }, + { + "code": "entity_not_found", + "type": "not_found", + "description": "No entity matches the id, slug, or name." + }, + { + "code": "feature_not_available", + "type": "permission_error", + "gate": "plan", + "description": "The plan does not include this feature. unlock.url names the plan that does." + }, + { + "code": "feedback_not_found", + "type": "not_found", + "description": "No feedback submission with this id for this account." + }, + { + "code": "filter_requires_paid", + "type": "permission_error", + "gate": "plan", + "description": "The parameter in param needs a paid plan; omit it for the free answer." + }, + { + "code": "forbidden", + "type": "permission_error", + "description": "The entity page list refused the request." + }, + { + "code": "freshness_requires_paid", + "type": "permission_error", + "gate": "freshness", + "description": "The date window is newer than the plan serves; move it before the cutoff or upgrade." + }, + { + "code": "id_required", + "type": "invalid_request_error", + "description": "A filter in param received a name where it takes an id (ent_{n} or UC...). Resolve the name first with GET /v1/entities/resolve and pass best.id or best.youtube_channel_id." + }, + { + "code": "idempotency_conflict", + "type": "conflict_error", + "description": "The Idempotency-Key was sent before with a different request body. Send a new key for a new request." + }, + { + "code": "idempotency_result_expired", + "type": "conflict_error", + "description": "The original one-time signing secret is no longer valid. Use a new request key for a new rotation." + }, + { + "code": "insufficient_scope", + "type": "permission_error", + "gate": "key", + "description": "The key lacks the scope this route needs." + }, + { + "code": "internal_error", + "type": "server_error", + "description": "The entity page list failed. Retry with backoff." + }, + { + "code": "invalid_api_key", + "type": "authentication_error", + "gate": "key", + "description": "No usable credential. reason says whether none was sent, it is unknown, or it was revoked." + }, + { + "code": "invalid_body", + "type": "invalid_request_error", + "description": "The JSON body failed validation; message names the field." + }, + { + "code": "invalid_cursor", + "type": "invalid_request_error", + "description": "The continuation is invalid, expired, or belongs to another query. Restart without cursor." + }, + { + "code": "invalid_email_recipients", + "type": "invalid_request_error", + "description": "Email recipients failed validation." + }, + { + "code": "invalid_feedback_request", + "type": "invalid_request_error", + "description": "The feedback query or corrections do not fit the feedback type." + }, + { + "code": "invalid_idempotency_key", + "type": "invalid_request_error", + "description": "Use a valid monitor creation request key." + }, + { + "code": "invalid_query", + "type": "invalid_request_error", + "description": "A query parameter failed validation; message names it." + }, + { + "code": "invalid_slack_integration", + "type": "invalid_request_error", + "description": "Choose an active Slack integration owned by the account." + }, + { + "code": "invalid_video_id", + "type": "invalid_request_error", + "description": "The video_id path segment is not an 11-character YouTube id." + }, + { + "code": "job_requires_account", + "type": "authentication_error", + "gate": "key", + "description": "Transcription jobs belong to an account; sign up for a key." + }, + { + "code": "list_unavailable", + "type": "server_error", + "description": "The entity page list failed with a status other than 400, 403, 404 or 500. Retry with backoff." + }, + { + "code": "max_charge_exceeded", + "type": "conflict_error", + "description": "The whole-video price or on-demand charge exceeds the caller-approved ceiling. Missing max_on_demand_cents authorizes zero monetary overage." + }, + { + "code": "max_rows_exceeded", + "type": "conflict_error", + "description": "The current whole-video quote exceeds the maximum authorized rows. Review the quote before submitting a new intent." + }, + { + "code": "method_not_allowed", + "type": "invalid_request_error", + "description": "The route accepts another HTTP method." + }, + { + "code": "monitor_creation_unavailable", + "type": "server_error", + "description": "Monitor creation is temporarily unavailable. Retry the same request with the same key." + }, + { + "code": "monitor_creation_unconfirmed", + "type": "server_error", + "description": "Monitor creation could not be confirmed. Retry the same request with the same key." + }, + { + "code": "monitor_not_found", + "type": "not_found", + "description": "No monitor with this id on the account." + }, + { + "code": "not_found", + "type": "not_found", + "description": "No route or resource at this path." + }, + { + "code": "pagination_gated", + "type": "permission_error", + "gate": "pagination", + "description": "Rows past the free window need a paid plan." + }, + { + "code": "premium_transcript_requested", + "type": "permission_error", + "gate": "exposure_law", + "description": "Premium transcript text needs a plan with Premium transcripts." + }, + { + "code": "purchase_required", + "type": "permission_error", + "description": "An explicit whole-video purchase is required. Read quote_url and submit max_rows with Idempotency-Key." + }, + { + "code": "quota_exceeded", + "type": "quota_exceeded", + "gate": "rows", + "description": "The row pool is spent. unlock.url upgrades or raises the limit." + }, + { + "code": "rate_limited", + "type": "rate_limit_error", + "gate": "rate", + "description": "Too many requests in the window. Retry-After carries the wait." + }, + { + "code": "recipient_upgrade_required", + "type": "permission_error", + "description": "The requested email recipient count exceeds the plan allowance." + }, + { + "code": "recommendations_not_enabled", + "type": "permission_error", + "gate": "plan", + "description": "Commercial data needs a Pro+ plan." + }, + { + "code": "resource_conflict", + "type": "conflict_error", + "description": "The operation conflicts with existing resource state." + }, + { + "code": "scope_too_broad", + "type": "invalid_request_error", + "description": "The requested exact video scope exceeds the search filename cap; narrow by channel or date." + }, + { + "code": "search_unavailable", + "type": "server_error", + "description": "Transcript search is unavailable (HTTP 503). Retry-After carries the wait." + }, + { + "code": "server_error", + "type": "server_error", + "description": "Unexpected failure. Retry with backoff and quote request_id if it persists." + }, + { + "code": "signup_code_invalid", + "type": "invalid_request_error", + "description": "The signup code is wrong, expired, or spent; message names the attempts left." + }, + { + "code": "signup_send_limited", + "type": "rate_limit_error", + "description": "Verification code sends hit a per-address, per-IP, or per-client cap. Retry-After carries the wait." + }, + { + "code": "team_key_required", + "type": "permission_error", + "description": "The route needs a team-scoped key; personal keys are refused." + }, + { + "code": "team_not_found", + "type": "not_found", + "description": "The key names a team that no longer exists." + }, + { + "code": "tracker_already_exists", + "type": "conflict_error", + "description": "The account already tracks this entity. existingId identifies the existing tracker." + }, + { + "code": "tracker_not_found", + "type": "not_found", + "description": "No tracker with this id on the account." + }, + { + "code": "transcript_fetching", + "type": "server_error", + "description": "The caption track is being fetched now (HTTP 503). Retry-After carries the wait; nothing was charged." + }, + { + "code": "transcript_requires_account", + "type": "authentication_error", + "gate": "key", + "description": "Full transcripts need an account key; unlock.action sends a signup code." + }, + { + "code": "transcript_unavailable", + "type": "not_found", + "description": "The video has no transcript in the requested lane or language; languages lists what exists." + }, + { + "code": "unknown_entity", + "type": "invalid_request_error", + "description": "An explicit entity_ids value does not resolve to a searchable entity." + }, + { + "code": "webhook_not_configured", + "type": "conflict_error", + "description": "The monitor has no webhook to rotate a secret for. Set webhookUrl first." + } + ] + }, + "HealthResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ], + "description": "Always \"ok\" when the API is reachable." + }, + "version": { + "type": "string", + "enum": [ + "v1" + ], + "description": "API version." + }, + "time": { + "type": "string", + "description": "Current server time (ISO 8601)." + } + }, + "required": [ + "status", + "version", + "time" + ] + }, + "OpenApiDocument": { + "type": "object", + "properties": { + "openapi": { + "type": "string", + "description": "OpenAPI version, 3.1.0." + }, + "info": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "API title." + }, + "version": { + "type": "string", + "description": "API version." + }, + "contact": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Contact name." + }, + "url": { + "type": "string", + "description": "Contact URL." + } + }, + "required": [ + "name", + "url" + ], + "description": "Who publishes the API." + } + }, + "required": [ + "title", + "version", + "contact" + ], + "description": "API metadata." + }, + "servers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "Base URL." + } + }, + "required": [ + "url" + ] + }, + "description": "Base URLs the API is served from." + }, + "paths": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": {} + }, + "description": "Every operation, keyed by path then HTTP method. An open map: each value is an OpenAPI path item object." + }, + "components": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": {} + }, + "description": "Shared schemas and security schemes, keyed by component kind then name. An open map of OpenAPI component objects." + } + }, + "required": [ + "openapi", + "info", + "servers", + "paths", + "components" + ], + "description": "This OpenAPI 3.1 document, the one you are reading." + }, + "MeResponse": { + "type": "object", + "properties": { + "user_id": { + "type": [ + "string", + "null" + ], + "description": "Id of the user the API key belongs to." + }, + "key_id": { + "type": [ + "string", + "null" + ], + "description": "Id of the credential making this request: the account key id, or the OAuth token id. Never a secret. Null on a browser session." + }, + "key_label": { + "type": [ + "string", + "null" + ], + "description": "The key name set in the dashboard, or the name of the connected OAuth client. Null when none is known." + }, + "credential_kind": { + "type": "string", + "enum": [ + "account_key", + "oauth", + "session" + ], + "description": "How the request authenticated: account_key is an arc_sk_ key, oauth is a token from a connected client, session is a signed-in browser." + }, + "email_masked": { + "type": [ + "string", + "null" + ], + "description": "The account email with the local part masked after its first character, e.g. z***@example.com. Null when the account has none." + }, + "period_resets_at": { + "type": [ + "string", + "null" + ], + "description": "ISO 8601 time the monthly row pool resets: 00:00 UTC on the first of next month. Null on the free plan, whose rows are a lifetime pool." + }, + "tier": { + "type": "string", + "description": "Plan tier, e.g. free, hobby, pro, teams, enterprise." + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Scopes granted to this API key, e.g. read, monitors:write, trackers:write, recommendations:read." + }, + "rate_limit": { + "type": "integer", + "description": "Requests allowed per 60-second window for this key: 600 for enterprise/teams, 240 for other paid tiers, 60 for free, unless a per-key override is set." + }, + "recommendations_api_enabled": { + "type": "boolean", + "description": "True when the plan includes the Recommendations API (commercial intelligence endpoints)." + }, + "usage": { + "type": "object", + "properties": { + "rows_used": { + "type": "integer", + "description": "Premium rows consumed this period." + }, + "rows_remaining": { + "type": "integer", + "description": "Premium rows left this period." + }, + "monthly_rows": { + "type": "integer", + "description": "Total premium rows included per period." + }, + "current_spend_cents": { + "type": "integer", + "description": "On-demand overage spend so far this period, in US cents." + }, + "credits": { + "type": "object", + "properties": { + "available": { + "type": [ + "integer", + "null" + ], + "description": "Credits spendable now: plan, granted, purchased, and on-demand up to its cap. Null when nothing limits it." + }, + "plan": { + "type": "object", + "properties": { + "credits": { + "type": [ + "integer", + "null" + ], + "description": "Plan credits this month. Null when the plan has no limit." + }, + "used": { + "type": "integer", + "description": "Plan credits spent this month." + }, + "resets_at": { + "type": "string", + "description": "YYYY-MM-DD, the first day of next month (UTC), when plan credits reset." + } + }, + "required": [ + "credits", + "used", + "resets_at" + ] + }, + "granted": { + "type": "integer", + "description": "Credits left in granted lots that have not expired." + }, + "purchased": { + "type": "integer", + "description": "Credits left in purchased top-ups." + }, + "on_demand": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "True when on-demand credits are on." + }, + "cap_credits": { + "type": [ + "integer", + "null" + ], + "description": "The on-demand cap in credits, at $0.002 a credit. Null when uncapped or off." + }, + "used": { + "type": "integer", + "description": "On-demand credits spent this month." + } + }, + "required": [ + "enabled", + "cap_credits", + "used" + ] + } + }, + "required": [ + "available", + "plan", + "granted", + "purchased", + "on_demand" + ], + "description": "The month in credits (1 credit is $0.001; a row is 4 credits). Present only when the credits ledger decides access." + }, + "hits": { + "type": "object", + "properties": { + "used": { + "type": "integer", + "description": "Monitor alerts delivered this month." + }, + "allowance": { + "type": [ + "integer", + "null" + ], + "description": "Monitor alerts the plan delivers a month. Null when the plan has no limit." + }, + "resets_at": { + "type": "string", + "description": "YYYY-MM-DD, the first day of next month (UTC), when the count resets." + } + }, + "required": [ + "used", + "allowance", + "resets_at" + ], + "description": "Monitor alerts this month. Alerts cost no credits; once the allowance is used, monitors keep matching but deliver nothing until the reset. Present only when the credits ledger decides access." + } + }, + "required": [ + "rows_used", + "rows_remaining", + "monthly_rows", + "current_spend_cents" + ] + }, + "settings": { + "$ref": "#/components/schemas/AccountSettings" + } + }, + "required": [ + "user_id", + "key_id", + "key_label", + "credential_kind", + "email_masked", + "period_resets_at", + "tier", + "scopes", + "rate_limit", + "recommendations_api_enabled", + "usage", + "settings" + ] + }, + "AccountSettings": { + "type": "object", + "properties": { + "transcripts": { + "$ref": "#/components/schemas/TranscriptSettings" + } + }, + "required": [ + "transcripts" + ], + "description": "Account defaults every key of this account resolves against. Set them with PATCH /v1/me/settings." + }, + "TranscriptSettings": { + "type": "object", + "properties": { + "quality": { + "type": "string", + "enum": [ + "captions", + "premium" + ], + "description": "Default transcript quality. Platform default: captions." + }, + "language": { + "type": "string", + "description": "Default comma-separated caption language priority list, tried in order. Platform default: en." + }, + "timestamps": { + "type": "boolean", + "description": "Default for the timestamps parameter. Platform default: true, which returns lines[]." + } + }, + "required": [ + "quality", + "language", + "timestamps" + ], + "description": "What a transcript request that names no parameter of its own receives." + }, + "MeSettingsResponse": { + "type": "object", + "properties": { + "settings": { + "$ref": "#/components/schemas/AccountSettings" + } + }, + "required": [ + "settings" + ] + }, + "SignupSentResponse": { + "type": "object", + "properties": { + "next": { + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": [ + "POST" + ], + "description": "Always POST." + }, + "url": { + "type": "string", + "description": "The verify call, absolute: POST it with { email, code } once the code arrives." + } + }, + "required": [ + "method", + "url" + ], + "description": "The call that completes the signup." + }, + "expires_in": { + "type": "integer", + "description": "Seconds the code stays valid, from the moment it was sent." + } + }, + "required": [ + "next", + "expires_in" + ] + }, + "SignupVerifiedResponse": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The arc_sk_ account key. Shown once. Send it as Authorization: Bearer." + }, + "key_id": { + "type": "string", + "description": "The key id. Appears on funnel events and in the dashboard; never a secret." + }, + "header": { + "type": "string", + "description": "The pasteable request header line: Authorization: Bearer arc_sk_..." + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Always [\"read\"]. Create an Admin key in the dashboard for account write access." + }, + "tier": { + "type": "string", + "description": "The plan the account is on: free for a new account, its plan when the address already had one." + }, + "rows_allotted": { + "type": "integer", + "description": "The pool this key draws on: a free account's lifetime credits, a paid plan's monthly rows." + }, + "next": { + "type": "string", + "description": "A curl command for the first call: GET /v1/me with the key." + }, + "docs_url": { + "type": "string", + "description": "Sign-up documentation." + } + }, + "required": [ + "key", + "key_id", + "header", + "scopes", + "tier", + "rows_allotted", + "next", + "docs_url" + ] + }, + "SearchResolveResponse": { + "type": "object", + "properties": { + "found": { + "type": "boolean", + "description": "True when the query resolved to exactly one entity." + }, + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id. Use \"ent_{id}\" with the /v1/entities endpoints." + }, + "name": { + "type": "string", + "description": "Canonical entity name." + }, + "type": { + "type": "string", + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + } + }, + "required": [ + "id", + "name", + "type" + ], + "description": "The resolved entity. Absent when found is false." + }, + "route": { + "type": "string", + "description": "Site-relative route for the entity on arcmira.com. Absent when found is false." + }, + "fromCache": { + "type": "boolean", + "description": "True when the result was served from the 5-minute resolver cache." + } + }, + "required": [ + "found" + ] + }, + "EntitySearchResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntitySearchResult" + } + }, + "query": { + "type": "string", + "description": "The q parameter echoed back." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows matched than limit. There is no cursor; narrow q or pass type." + } + }, + "required": [ + "data", + "query", + "has_more" + ] + }, + "EntitySearchResult": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public entity id in the form \"ent_{n}\"." + }, + "numeric_id": { + "type": "integer", + "description": "Raw integer database id." + }, + "name": { + "type": "string", + "description": "Entity name." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug. Null when the entity has never been slugged." + }, + "type": { + "type": "string", + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + }, + "appearance_count": { + "type": "integer", + "default": 0, + "description": "Number of indexed appearance/mention rows. Results are ordered by this, descending." + }, + "youtube_channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id for channel entities. Null for every other type." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "One catalog sentence that tells rows with the same name apart, for example \"Common gender-neutral given name or nickname\". Null when the catalog has none." + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for." + }, + "suggested": { + "type": "boolean", + "description": "True on an exact match with far more traction than any other row of its type, or on the one row a UC id, @handle, or YouTube URL resolves to. Use it without asking." + }, + "recommendations_summary": { + "type": "object", + "properties": { + "total_ad_reads": { + "type": "integer", + "description": "Total ad_read rows for this entity. 0 when none." + }, + "total_endorsements": { + "type": "integer", + "description": "Total endorsement rows for this entity. 0 when none." + }, + "unique_channels": { + "type": "integer", + "description": "Number of distinct channels with commercial mentions of this entity." + } + }, + "required": [ + "total_ad_reads", + "total_endorsements", + "unique_channels" + ], + "description": "Only present when the caller has Recommendations API access (a Pro+ plan) and a brand profile exists for the entity." + } + }, + "required": [ + "id", + "numeric_id", + "name", + "slug", + "type", + "youtube_channel_id", + "description", + "page", + "suggested" + ] + }, + "EntityResolveResponse": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The q parameter echoed back." + }, + "context": { + "type": [ + "string", + "null" + ], + "description": "The context parameter echoed back." + }, + "confidence": { + "type": "string", + "enum": [ + "exact", + "single_fuzzy", + "ambiguous", + "fuzzy", + "none" + ], + "description": "exact: one row is named q (or the handle, id or alias), and no better-known person carries the name. single_fuzzy: the only row returned, not an exact name. ambiguous: several exact rows, or an exact row next to a better-known person sharing the name (Jordan the brand vs Michael Jordan). fuzzy: only loose matches. none: no row." + }, + "best": { + "$ref": "#/components/schemas/ResolveCandidate" + }, + "suggested": { + "$ref": "#/components/schemas/ResolveSuggestion" + }, + "ask": { + "type": [ + "object", + "null" + ], + "properties": { + "question": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "label": { + "type": "string", + "description": "One line to show the user: name, type, description, appearance count." + } + }, + "required": [ + "id", + "name", + "type", + "label" + ] + } + } + }, + "required": [ + "question", + "options" + ], + "description": "Set when best and suggested are both null and several rows fit: show the options to the user, or check every option id against the data and answer per row." + }, + "candidates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResolveCandidate" + }, + "description": "Rows considered: exact names first, then initials, whole-word, spelling and substring matches, each by appearance count." + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this." + } + }, + "required": [ + "query", + "context", + "confidence", + "best", + "suggested", + "ask", + "candidates", + "note" + ], + "example": { + "query": "Sam", + "context": null, + "confidence": "ambiguous", + "best": null, + "suggested": { + "id": "ent_124463", + "name": "Sam Altman", + "slug": "sam-altman", + "type": "person", + "appearance_count": 4399, + "youtube_channel_id": null, + "description": "CEO of OpenAI and former president of Y Combinator.", + "page": "https://arcmira.com/person/sam-altman", + "match": "word", + "reason": "dominant", + "evidence": "it has 4,399 appearances, 11x the next match (Sam Dunning, 397)", + "assumed": true + }, + "ask": null, + "candidates": [ + { + "id": "ent_123696", + "name": "Sam", + "slug": "sam", + "type": "person", + "appearance_count": 265, + "youtube_channel_id": null, + "description": "Common gender-neutral given name or nickname", + "page": "https://arcmira.com/person/sam", + "match": "exact" + } + ], + "note": "No row is certain. suggested is Sam Altman (person) because it has 4,399 appearances, 11x the next match (Sam Dunning, 397). Use it and tell the user you assumed it." + } + }, + "ResolveCandidate": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string", + "description": "Public entity id in the form \"ent_{n}\"." + }, + "name": { + "type": "string", + "description": "Entity name." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug. Null when the entity has never been slugged." + }, + "type": { + "type": "string", + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + }, + "appearance_count": { + "type": "integer", + "default": 0, + "description": "Number of indexed appearance/mention rows. Results are ordered by this, descending." + }, + "youtube_channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id for channel entities. Null for every other type." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "One catalog sentence that tells rows with the same name apart, for example \"Common gender-neutral given name or nickname\". Null when the catalog has none." + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for." + }, + "match": { + "type": "string", + "enum": [ + "exact", + "word", + "substring", + "acronym", + "spelling" + ], + "description": "How the row's name relates to q: the whole name, a run of its words (Michael Jordan for Jordan), characters inside a word, the show's initials (My First Million for MFM), or a near spelling." + } + }, + "required": [ + "id", + "name", + "slug", + "type", + "youtube_channel_id", + "description", + "page", + "match" + ], + "description": "The one row q means. Set on exact and single_fuzzy only. Name it in the answer." + }, + "ResolveSuggestion": { + "allOf": [ + { + "$ref": "#/components/schemas/ResolveCandidate" + }, + { + "type": [ + "object", + "null" + ], + "properties": { + "reason": { + "type": "string", + "enum": [ + "dominant", + "only_word_match", + "context", + "acronym", + "spelling" + ], + "description": "Why this row stands out: dominant (10x the appearances of the next match), only_word_match, context (the context parameter points at it), acronym, spelling." + }, + "evidence": { + "type": "string", + "description": "The numbers or words behind the reason, to repeat to the user." + }, + "assumed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: this is an assumption the answer must state." + } + }, + "required": [ + "reason", + "evidence", + "assumed" + ] + } + ], + "description": "Set when best is null but one row stands out, with the reason and evidence. Use it and tell the user you assumed it." + }, + "EntityLookupResponse": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/Entity" + } + }, + "required": [ + "entity" + ] + }, + "Entity": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public entity id in the form \"ent_{n}\". Always the canonical entity id." + }, + "numeric_id": { + "type": "integer", + "description": "Raw integer database id of the canonical entity. Prefer the public \"ent_{n}\" id in requests." + }, + "canonical_id": { + "type": "string", + "description": "Public id of the canonical entity. Identical to id." + }, + "name": { + "type": "string", + "description": "Canonical entity name." + }, + "type": { + "type": "string", + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + }, + "platform": { + "type": [ + "string", + "null" + ], + "description": "Source platform for channel entities, e.g. \"youtube\". Null unless the entity is platform-bound." + }, + "url": { + "type": [ + "string", + "null" + ], + "description": "Canonical external URL for the entity. Null when none is known." + }, + "image_url": { + "type": [ + "string", + "null" + ], + "description": "Entity image URL. Null until an image has been resolved." + }, + "image_checked_at": { + "type": [ + "string", + "null" + ], + "description": "Timestamp of the last image resolution attempt. Null until the image pipeline has visited this entity." + }, + "appearance_count": { + "type": "integer", + "default": 0, + "description": "Number of indexed appearance/mention rows for this entity. 0 when never counted." + }, + "owner_entity_id": { + "type": [ + "string", + "null" + ], + "description": "Public id (\"ent_{n}\") of the owning entity, e.g. the organization behind a product. Null unless an ownership link exists." + }, + "is_canonical": { + "type": "boolean", + "description": "True when the id you supplied is the canonical entity. False when your id was merged into this canonical record." + }, + "merged_from_id": { + "type": [ + "string", + "null" + ], + "description": "Public id you supplied when it differs from the canonical entity, i.e. your id was merged into this record. Null unless a merge redirect happened." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug, the site's canonical id for every type but channel. Null when never slugged." + }, + "route": { + "type": [ + "string", + "null" + ], + "description": "Site-relative route of this entity's page on arcmira.com, e.g. \"/org/ramp\", \"/person/jane-doe\", \"/yt/@TBPNLive\". Null for a type the site has no page for." + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for." + }, + "appearances_page": { + "type": [ + "string", + "null" + ], + "description": "For a person, the list of their appearances on arcmira.com (their page opens on it). Null for every other type." + }, + "mentions_page": { + "type": [ + "string", + "null" + ], + "description": "For a person, the list of mentions of them on arcmira.com, page/mentions. Null for every other type: their page is the mentions list already." + } + }, + "required": [ + "id", + "numeric_id", + "canonical_id", + "name", + "type", + "platform", + "url", + "image_url", + "image_checked_at", + "owner_entity_id", + "is_canonical", + "merged_from_id", + "slug", + "route", + "page", + "appearances_page", + "mentions_page" + ] + }, + "EntityCardsResponse": { + "type": "object", + "properties": { + "cards": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityCard" + }, + "description": "One card per requested id that resolved to an entity, in requested order. Unknown ids are silently dropped." + } + }, + "required": [ + "cards" + ] + }, + "EntityCard": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The REQUESTED raw integer entity id (even when it was merged; the card carries the canonical entity's data under the requested id)." + }, + "name": { + "type": "string", + "description": "Canonical entity name." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug. Null when the entity has never been slugged." + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + }, + "image_url": { + "type": [ + "string", + "null" + ], + "description": "Entity image URL. Null until an image has been resolved." + }, + "subtitle": { + "type": "null", + "description": "Reserved for a future category/role line. Always null today." + }, + "index_mentions": { + "type": "integer", + "description": "Total indexed mention rows for the entity. 0 when none." + }, + "index_appearances": { + "type": [ + "integer", + "null" + ], + "description": "Indexed physical-appearance rows. Only populated for person entities; null for every other type." + }, + "indexed_videos": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "description": "Completed videos published by this channel and indexed by Arcmira. Null for other entity types." + }, + "average_views": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Mean of known positive view counts for the channel's indexed videos. Null when unavailable or for other types." + }, + "youtube_channel_id": { + "type": [ + "string", + "null" + ], + "description": "Canonical YouTube channel identifier, when known." + }, + "youtube_handle": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel handle, when known." + } + }, + "required": [ + "id", + "name", + "slug", + "type", + "image_url", + "subtitle", + "index_mentions", + "index_appearances" + ] + }, + "EntityDetailResponse": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "recommendations_summary": { + "$ref": "#/components/schemas/EntityDetailRecommendationsSummary" + } + }, + "required": [ + "entity" + ] + }, + "EntityDetailRecommendationsSummary": { + "type": "object", + "properties": { + "total_ad_reads": { + "type": "integer", + "description": "Total ad_read rows across all channels. 0 when none." + }, + "total_endorsements": { + "type": "integer", + "description": "Total endorsement rows across all channels. 0 when none." + }, + "unique_shows": { + "type": "integer", + "description": "Number of distinct shows/channels with commercial mentions of this entity." + }, + "first_seen_at": { + "type": [ + "string", + "null" + ], + "description": "Timestamp of the earliest commercial mention. Null until the brand profile has been computed." + }, + "last_seen_at": { + "type": [ + "string", + "null" + ], + "description": "Timestamp of the most recent commercial mention. Null until the brand profile has been computed." + }, + "channels_as_sponsor": { + "type": "integer", + "description": "Number of channels where this entity appears in the curated known-advertisers dataset." + } + }, + "required": [ + "total_ad_reads", + "total_endorsements", + "unique_shows", + "first_seen_at", + "last_seen_at", + "channels_as_sponsor" + ], + "description": "Commercial-intelligence rollup. Only present for organization and product entities when the caller has Recommendations API access (a Pro+ plan) and a brand profile exists." + }, + "MentionListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Mention" + } + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "entity": { + "allOf": [ + { + "$ref": "#/components/schemas/Entity" + }, + { + "description": "The resolved entity the mentions belong to." + } + ] + }, + "note": { + "type": "string", + "description": "Present on a free preview page: where the list stops and the plan that lifts it. Say so rather than calling this every mention." + }, + "unlock": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "The plan that lifts the preview." + }, + "url": { + "type": "string", + "description": "Where to start that plan." + } + }, + "required": [ + "tier", + "url" + ], + "description": "Present with note on a free preview page." + } + }, + "required": [ + "data", + "has_more", + "next_cursor", + "entity" + ] + }, + "Mention": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public mention id in the form \"men_{n}\"." + }, + "appearance_id": { + "type": "integer", + "description": "Raw integer id of the underlying appearance row. Same number as in the \"men_{n}\" public id." + }, + "entity": { + "$ref": "#/components/schemas/EntityRef" + }, + "media": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer media row id." + }, + "video_id": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title. Null when the video was indexed without metadata." + }, + "url": { + "type": [ + "string", + "null" + ], + "description": "Video URL. Null when unknown." + }, + "published_at": { + "type": [ + "string", + "null" + ], + "description": "Video publish timestamp. Null when unknown." + }, + "channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel. Null when unknown." + }, + "view_count": { + "type": [ + "integer", + "null" + ], + "description": "Video view count at index time. Null when never fetched." + }, + "source_channel": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string", + "description": "Public entity id (\"ent_{n}\") of the source channel." + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Source channel name." + }, + "url": { + "type": [ + "string", + "null" + ], + "description": "Source channel URL. Null when unknown." + } + }, + "required": [ + "id", + "name", + "url" + ], + "description": "The channel entity that published the video. Null when the video has not been linked to a channel entity." + } + }, + "required": [ + "id", + "video_id", + "title", + "url", + "published_at", + "channel_id", + "view_count", + "source_channel" + ] + }, + "start_timestamp": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "DEPRECATED: prefer the numeric sibling field. This \"MM:SS\" (or \"HH:MM:SS\") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds. Null when the analyzer could not locate the mention in time." + }, + "end_timestamp": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "DEPRECATED: prefer the numeric sibling field. This \"MM:SS\" (or \"HH:MM:SS\") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds. Null when unknown." + }, + "start_seconds": { + "type": [ + "integer", + "null" + ], + "description": "Start position in the video in integer SECONDS, parsed from start_timestamp. Prefer this over the deprecated string field. 0 means \"full episode / no specific moment\" (the string sentinel \"00:00\"). Null when the string timestamp is null or unparseable." + }, + "end_seconds": { + "type": [ + "integer", + "null" + ], + "description": "End position in the video in integer SECONDS, parsed from end_timestamp. 0 means \"full episode / no specific moment\" (the string sentinel \"00:00\"). Null when the string timestamp is null or unparseable." + }, + "is_appearance": { + "type": "boolean", + "description": "True when the person physically appears/speaks in the media (person entities only). Always false for organization, product, topic, and channel entities. Filtering with is_appearance=true on a non-person entity returns a 400 (appearances_person_only)." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "One-sentence description of the mention context. Null when not generated." + }, + "confidence": { + "type": [ + "number", + "null" + ], + "description": "Analyzer confidence between 0 and 1. Null for legacy rows analyzed before confidence scoring." + }, + "sentiment_score": { + "type": [ + "number", + "null" + ], + "description": "Raw sentiment score between -1 and 1. Null when sentiment was not computed for this mention." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Derived sentiment label from sentiment_score. Values: positive (score above 0.2), negative (score below -0.2), neutral (score between -0.2 and 0.2 inclusive, or no score computed)." + }, + "referenced_url": { + "type": [ + "string", + "null" + ], + "description": "URL referenced in the mention. Null unless one was extracted." + }, + "referenced_platform": { + "type": [ + "string", + "null" + ], + "description": "Platform referenced in the mention, e.g. \"twitter\". Null unless one was extracted." + }, + "extracted_content": { + "type": [ + "string", + "null" + ], + "description": "Verbatim content extracted for the mention. Null unless extraction ran." + }, + "recommendations": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecommendationEnrichmentItem" + }, + "description": "Commercial mentions (ad reads, endorsements) for the same entity in the same video." + } + }, + "required": [ + "items" + ], + "description": "Only present when the request used details=full (requires a Pro+ plan)." + } + }, + "required": [ + "id", + "appearance_id", + "entity", + "media", + "start_timestamp", + "end_timestamp", + "start_seconds", + "end_seconds", + "is_appearance", + "description", + "confidence", + "sentiment_score", + "sentiment", + "referenced_url", + "referenced_platform", + "extracted_content" + ] + }, + "EntityRef": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public entity id in the form \"ent_{n}\"." + }, + "name": { + "type": "string", + "description": "Canonical entity name." + }, + "type": { + "type": "string", + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug, the site's canonical id for every type but channel. Null when never slugged." + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for." + } + }, + "required": [ + "id", + "name", + "type", + "slug", + "page" + ] + }, + "RecommendationEnrichmentItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public recommendation id in the form \"com_{n}\"." + }, + "mention_class": { + "type": "string", + "description": "Commercial mention classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention)." + }, + "verbatim_quote": { + "type": [ + "string", + "null" + ], + "description": "Verbatim quote from the transcript. Null when no quote was extracted." + }, + "promo_code": { + "type": [ + "string", + "null" + ], + "description": "Promo code read out in the mention. Null unless one was detected." + }, + "offer": { + "type": [ + "string", + "null" + ], + "description": "Offer text, e.g. \"20% off\". Null unless one was detected." + }, + "confidence": { + "type": "number", + "description": "Classifier confidence between 0 and 1." + }, + "start_timestamp": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED: prefer the numeric sibling field. This \"MM:SS\" (or \"HH:MM:SS\") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds." + }, + "end_timestamp": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED: prefer the numeric sibling field. This \"MM:SS\" (or \"HH:MM:SS\") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds." + }, + "start_seconds": { + "type": [ + "integer", + "null" + ], + "description": "Start position in the video in integer SECONDS, parsed from start_timestamp. 0 means \"full episode / no specific moment\" (the string sentinel \"00:00\"). Null when the string timestamp is null or unparseable." + }, + "end_seconds": { + "type": [ + "integer", + "null" + ], + "description": "End position in the video in integer SECONDS, parsed from end_timestamp. 0 means \"full episode / no specific moment\" (the string sentinel \"00:00\"). Null when the string timestamp is null or unparseable." + } + }, + "required": [ + "id", + "mention_class", + "verbatim_quote", + "promo_code", + "offer", + "confidence", + "start_timestamp", + "end_timestamp", + "start_seconds", + "end_seconds" + ] + }, + "RecommendationListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recommendation" + } + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "entity": { + "allOf": [ + { + "$ref": "#/components/schemas/Entity" + }, + { + "description": "The resolved entity the recommendations belong to." + } + ] + } + }, + "required": [ + "data", + "has_more", + "next_cursor", + "entity" + ] + }, + "Recommendation": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public recommendation id in the form \"com_{n}\"." + }, + "recommendation_id": { + "type": "integer", + "description": "Raw integer id of the recommendation row. Same number as in the \"com_{n}\" public id." + }, + "mention_class": { + "type": "string", + "description": "Commercial mention classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention)." + }, + "entity": { + "$ref": "#/components/schemas/EntityRef" + }, + "media": { + "type": "object", + "properties": { + "video_id": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "published_at": { + "type": [ + "string", + "null" + ], + "description": "Video publish timestamp." + }, + "channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel." + }, + "source_channel": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string", + "description": "Public entity id (\"ent_{n}\") of the source channel." + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Source channel name." + } + }, + "required": [ + "id", + "name" + ], + "description": "The channel entity that published the video. Null when the video has not been linked to a channel entity." + } + }, + "required": [ + "video_id", + "title", + "published_at", + "channel_id", + "source_channel" + ] + }, + "start_timestamp": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED: prefer the numeric sibling field. This \"MM:SS\" (or \"HH:MM:SS\") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds." + }, + "end_timestamp": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED: prefer the numeric sibling field. This \"MM:SS\" (or \"HH:MM:SS\") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds." + }, + "start_seconds": { + "type": [ + "integer", + "null" + ], + "description": "Start position in the video in integer SECONDS, parsed from start_timestamp. Prefer this over the deprecated string field. 0 means \"full episode / no specific moment\" (the string sentinel \"00:00\"). Null when the string timestamp is null or unparseable." + }, + "end_seconds": { + "type": [ + "integer", + "null" + ], + "description": "End position in the video in integer SECONDS, parsed from end_timestamp. 0 means \"full episode / no specific moment\" (the string sentinel \"00:00\"). Null when the string timestamp is null or unparseable." + }, + "verbatim_quote": { + "type": [ + "string", + "null" + ], + "description": "Verbatim quote from the transcript. Null when no quote was extracted." + }, + "promo_code": { + "type": [ + "string", + "null" + ], + "description": "Promo code read out in the mention. Null unless one was detected." + }, + "offer": { + "type": [ + "string", + "null" + ], + "description": "Offer text, e.g. \"20% off your first order\". Null unless one was detected." + }, + "sentiment": { + "type": [ + "number", + "null" + ], + "deprecated": true, + "description": "DEPRECATED: use sentiment_score, which carries the same number. Removal will be announced in the changelog. Raw NUMERIC sentiment score between -1 and 1. Null when not computed." + }, + "sentiment_score": { + "type": [ + "number", + "null" + ], + "description": "Raw sentiment score between -1 and 1, same semantics as sentiment_score on mention rows. Null when not computed." + }, + "confidence": { + "type": "number", + "description": "Classifier confidence between 0 and 1. Rows below the min_confidence filter (default 0.7) are excluded from list responses." + }, + "speaker_role": { + "type": "string", + "description": "Role of the speaker delivering the mention, e.g. \"host\" or \"guest\"." + }, + "conflict_status": { + "type": [ + "string", + "null" + ], + "description": "Set when community feedback disputes the classification (e.g. \"disputed\"). Null when undisputed. Disputed rows are excluded unless include_disputed=true." + }, + "resolution": { + "type": [ + "string", + "null" + ], + "description": "How a disputed classification was resolved. Null until a dispute has been resolved." + } + }, + "required": [ + "id", + "recommendation_id", + "mention_class", + "entity", + "media", + "start_timestamp", + "end_timestamp", + "start_seconds", + "end_seconds", + "verbatim_quote", + "promo_code", + "offer", + "sentiment", + "sentiment_score", + "confidence", + "speaker_role", + "conflict_status", + "resolution" + ] + }, + "FeedbackResponse": { + "type": "object", + "properties": { + "feedback_id": { + "type": "integer", + "description": "Id of the persisted feedback record. Read it back via GET /v1/feedback/{feedback_id}." + }, + "type": { + "type": "string", + "description": "The feedback type you submitted. Values: recommendations, channel_sponsors, mentions, entities_search, entities, channels, monitor_alert, appearances, search." + }, + "query": { + "type": "object", + "additionalProperties": {}, + "description": "The query object the feedback is attached to, echoed back." + }, + "applied": { + "type": "integer", + "description": "Count of corrections applied automatically. CURRENTLY always 0: public submissions are logged for review, never auto-applied." + }, + "unchanged": { + "type": "integer", + "description": "Count of corrections whose target already had the requested value. Currently always 0 for public submissions." + }, + "failed": { + "type": "integer", + "description": "Count of corrections that could not be processed. Currently always 0 for public submissions." + }, + "logged": { + "type": "integer", + "description": "Count of corrections recorded for human review." + }, + "corrections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FeedbackCorrectionResult" + }, + "description": "Per-correction outcomes, in submission order." + } + }, + "required": [ + "feedback_id", + "type", + "query", + "applied", + "unchanged", + "failed", + "logged", + "corrections" + ] + }, + "FeedbackCorrectionResult": { + "type": "object", + "properties": { + "item_id": { + "type": "string", + "description": "The correction target id you supplied (or a generated placeholder when omitted)." + }, + "item_kind": { + "type": "string", + "description": "Inferred kind of the target. Values include: recommendation, sponsor_entity, entity, entity_merge, mention, appearance, alert, alert_expectation, unknown." + }, + "status": { + "type": "string", + "enum": [ + "applied", + "unchanged", + "not_found", + "invalid", + "logged" + ], + "description": "Outcome. Values: applied (the correction was applied automatically), unchanged (the target already had the requested value), not_found (the target does not exist), invalid (the correction payload was malformed for its kind), logged (recorded for human review; no automatic apply)." + }, + "previous_mention_class": { + "type": [ + "string", + "null" + ], + "description": "The mention_class before the correction. Only present for recommendation-class corrections." + }, + "new_mention_class": { + "type": "string", + "description": "The mention_class requested. Only present for recommendation-class corrections." + }, + "rows_affected": { + "type": "integer", + "description": "Rows updated by an applied correction." + }, + "reason": { + "type": "string", + "description": "The reason code you supplied, echoed back." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome." + }, + "recommendation": { + "allOf": [ + { + "$ref": "#/components/schemas/Recommendation" + }, + { + "description": "The recommendation after the correction. Only present for recommendation-class corrections that resolved a row." + } + ] + } + }, + "required": [ + "item_id", + "item_kind", + "status" + ] + }, + "MergeSuggestionChange": { + "type": "object", + "properties": { + "sourceEntityId": { + "type": "string", + "description": "Public id (\"ent_{n}\") of the duplicate/variant entity to merge away." + }, + "targetEntityId": { + "type": "string", + "description": "Public id (\"ent_{n}\") of the canonical entity to merge into." + }, + "sourceName": { + "type": "string", + "description": "Name of the duplicate entity when you do not have its id." + }, + "merge_into": { + "type": "string", + "description": "Name or public id of the canonical entity when you do not have targetEntityId." + }, + "scopeType": { + "type": "string", + "description": "Scope of the merge rule, e.g. \"global\"." + } + }, + "additionalProperties": {}, + "description": "For issue_type merge_suggestion (and duplicate_entity): the canonical merge you are proposing. Provide ids when you have them, names otherwise." + }, + "WrongEntityChange": { + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "description": "Public id (\"ent_{n}\") of the entity the row should point at." + }, + "entity_name": { + "type": "string", + "description": "Name of the correct entity when you do not have its id." + }, + "entity_type": { + "type": "string", + "description": "Type of the correct entity: person, organization, product, topic, or channel." + } + }, + "additionalProperties": {}, + "description": "For issue_type wrong_entity (and wrong_person): the entity the row should have been attributed to." + }, + "WrongEntityTypeChange": { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "type" + ], + "description": "Always \"type\"." + }, + "value": { + "type": "string", + "description": "The correct entity type: person, organization, product, topic, or channel." + } + }, + "additionalProperties": {}, + "description": "For issue_type wrong_entity_type: { \"field\": \"type\", \"value\": \"organization\" }." + }, + "MissingResultChange": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the missing entity or result." + }, + "entity_type": { + "type": "string", + "description": "Type of the missing entity: person, organization, product, topic, or channel." + }, + "source_url": { + "type": "string", + "description": "URL evidencing the missing result (video, channel, or article)." + } + }, + "additionalProperties": {}, + "description": "For issue_type missing_result: what should have been returned. Put video/channel/timestamp context in notes." + }, + "WrongClassificationChange": { + "type": "object", + "properties": { + "mention_class": { + "type": "string", + "enum": [ + "ad_read", + "endorsement", + "mention" + ], + "description": "The correct commercial classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention)." + } + }, + "additionalProperties": {}, + "description": "For issue_type wrong_classification: the commercial class the row should carry." + }, + "StaleMetadataChange": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "The metadata field that is outdated, e.g. \"website\" or \"name\"." + }, + "value": { + "description": "The current, correct value." + }, + "source_url": { + "type": "string", + "description": "URL evidencing the correct value." + } + }, + "additionalProperties": {}, + "description": "For issue_type stale_metadata: the field and its correct value, with a source URL when you have one." + }, + "BadRankingChange": { + "type": "object", + "properties": { + "expected_rank": { + "type": "integer", + "description": "Where the row should have ranked (1-based)." + }, + "observed_rank": { + "type": "integer", + "description": "Where the row actually ranked (1-based). Most useful on search feedback." + } + }, + "additionalProperties": {}, + "description": "For issue_type bad_ranking: the expected and observed positions of the row." + }, + "MissedAlertChange": { + "type": "object", + "properties": { + "source_url": { + "type": "string", + "description": "URL of the media that should have produced an alert." + }, + "approximate_timestamp_seconds": { + "type": "number", + "description": "Approximate position of the missed occurrence, in seconds from the start of the media." + }, + "entity_id": { + "type": "string", + "description": "Public id (\"ent_{n}\") of the tracked entity the missed alert concerns." + } + }, + "required": [ + "source_url" + ], + "additionalProperties": {}, + "description": "For issue_type missed_alert: an expectation with no alert row to target. Omit the correction id and describe where the alert should have fired." + }, + "DeliveryIssueChange": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "enum": [ + "email", + "webhook", + "slack" + ], + "description": "The delivery channel the issue concerns. Values: email, webhook, slack." + } + }, + "required": [ + "channel" + ], + "additionalProperties": {}, + "description": "For issue_type delivery_issue: targets the delivery row (the correction id) and names the channel that was wrong or never received." + }, + "FreeformSuggestedChange": { + "type": "object", + "additionalProperties": {}, + "description": "Any other object shape. Accepted and logged verbatim for human review; prefer the typed shapes above when one fits your issue_type." + }, + "FeedbackReadbackResponse": { + "type": "object", + "properties": { + "feedback_id": { + "type": "integer", + "description": "Id of the feedback record." + }, + "type": { + "type": "string", + "description": "The feedback type as submitted. Values: recommendations, channel_sponsors, mentions, entities_search, entities, channels, monitor_alert, appearances, search." + }, + "status": { + "type": "string", + "enum": [ + "pending_review", + "needs_information", + "accepted", + "accepted_with_changes", + "rejected", + "withdrawn", + "applied", + "reverted" + ], + "description": "Submission-level rollup of the per-correction statuses. Review status in the public vocabulary. Values: pending_review (submitted; a reviewer has not finished with it), needs_information (a reviewer needs more detail from you; add context in a support thread quoting the feedback_id), accepted (the correction was accepted as submitted), accepted_with_changes (accepted, but the reviewer resolved it differently than proposed), rejected (reviewed and declined), withdrawn (withdrawn by the submitter before review), applied (the accepted change is live in the index; accepted does not imply applied), reverted (a previously applied change was rolled back)." + }, + "query": { + "type": "object", + "additionalProperties": {}, + "description": "The query object the feedback was attached to, as submitted." + }, + "notes": { + "type": [ + "string", + "null" + ], + "description": "The top-level notes as submitted. Null when none were supplied." + }, + "created_at": { + "type": [ + "string", + "null" + ], + "description": "When the submission was created." + }, + "corrections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FeedbackReadbackCorrection" + }, + "description": "Per-correction rows with their individual review statuses, in submission order." + } + }, + "required": [ + "feedback_id", + "type", + "status", + "query", + "notes", + "created_at", + "corrections" + ] + }, + "FeedbackReadbackCorrection": { + "type": "object", + "properties": { + "item_id": { + "type": "string", + "description": "The correction target id as submitted (or a generated placeholder when omitted)." + }, + "item_kind": { + "type": "string", + "description": "Inferred kind of the target. Values include: recommendation, sponsor_entity, entity, entity_merge, mention, appearance, alert, alert_expectation, unknown." + }, + "issue_type": { + "type": [ + "string", + "null" + ], + "description": "The issue_type as submitted. Null when the correction carried only a reason or mention_class." + }, + "reason": { + "type": [ + "string", + "null" + ], + "description": "The commercial reason code as submitted. Null unless the correction was a commercial-class dispute." + }, + "suggested_change": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "The suggested_change object as submitted. Null when none was supplied." + }, + "notes": { + "type": [ + "string", + "null" + ], + "description": "The per-correction notes as submitted. Null when none were supplied." + }, + "status": { + "type": "string", + "enum": [ + "pending_review", + "needs_information", + "accepted", + "accepted_with_changes", + "rejected", + "withdrawn", + "applied", + "reverted" + ], + "description": "Review status in the public vocabulary. Values: pending_review (submitted; a reviewer has not finished with it), needs_information (a reviewer needs more detail from you; add context in a support thread quoting the feedback_id), accepted (the correction was accepted as submitted), accepted_with_changes (accepted, but the reviewer resolved it differently than proposed), rejected (reviewed and declined), withdrawn (withdrawn by the submitter before review), applied (the accepted change is live in the index; accepted does not imply applied), reverted (a previously applied change was rolled back)." + }, + "resolution_note": { + "type": [ + "string", + "null" + ], + "description": "Reviewer-written public note about how the correction was resolved. Null until a reviewer leaves one." + }, + "created_at": { + "type": [ + "string", + "null" + ], + "description": "When the correction row was created." + } + }, + "required": [ + "item_id", + "item_kind", + "issue_type", + "reason", + "suggested_change", + "notes", + "status", + "resolution_note", + "created_at" + ] + }, + "ChannelSponsorsResponse": { + "type": "object", + "properties": { + "channel": { + "type": "object", + "properties": { + "id": { + "type": [ + "string", + "null" + ], + "description": "Public entity id (\"ent_{n}\") of the channel. Null when the channel has media in the index but no entity record yet." + }, + "youtube_channel_id": { + "type": "string", + "description": "YouTube channel id as supplied in the request path." + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Channel name. Null when no entity record exists." + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + } + }, + "required": [ + "id", + "youtube_channel_id", + "name", + "page" + ] + }, + "sponsors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelSponsor" + }, + "description": "Recurring sponsors ordered by ad read count (descending)." + }, + "meta": { + "type": "object", + "properties": { + "min_ad_reads": { + "type": "integer", + "description": "The min_ad_reads threshold applied (default 3)." + }, + "count": { + "type": "integer", + "description": "Number of sponsors returned." + }, + "total": { + "type": "integer", + "description": "Sponsors in the rollup at the applied threshold. Greater than count only when the plan gate cut the list to the free slice." + } + }, + "required": [ + "min_ad_reads", + "count", + "total" + ] + }, + "access": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error" + ], + "description": "The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling." + }, + "code": { + "type": "string", + "description": "The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first." + }, + "reason": { + "type": "string", + "enum": [ + "no_credential", + "invalid", + "revoked" + ], + "description": "Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable." + }, + "message": { + "type": "string", + "description": "One plain line. Names the fix or the unlock." + }, + "param": { + "type": "string", + "description": "The query or body parameter the gate refused, when one did." + }, + "gate": { + "type": "string", + "enum": [ + "rows", + "key", + "plan", + "freshness", + "exposure_law", + "rate", + "pagination" + ], + "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." + }, + "unlock": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "The plan that lifts the gate." + }, + "url": { + "type": "string", + "description": "Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim." + }, + "offer": { + "type": "null", + "description": "Reserved for the agent-discount offer. Always null today." + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the call does. send_signup_code sends a verification code to an address for an account key." + }, + "method": { + "type": "string", + "description": "HTTP method to use." + }, + "url": { + "type": "string", + "description": "Absolute endpoint carrying its ?src= attribution. Call it verbatim." + } + }, + "required": [ + "kind", + "method", + "url" + ], + "description": "The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens." + } + }, + "required": [ + "tier", + "url", + "offer" + ], + "description": "How to lift the gate. Present when the gate has an unlock." + }, + "retry_after_seconds": { + "type": "integer", + "description": "Present on rate gates. Mirrors the Retry-After header." + }, + "doc_url": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "type", + "code", + "message", + "doc_url", + "request_id" + ], + "description": "The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would." + } + }, + "required": [ + "channel", + "sponsors", + "meta" + ] + }, + "ChannelSponsor": { + "type": "object", + "properties": { + "entity": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityRef" + }, + { + "description": "The sponsoring entity." + } + ] + }, + "ad_reads": { + "type": "integer", + "description": "Number of ad_read recommendation rows for this sponsor on the channel." + }, + "videos": { + "type": "integer", + "description": "Number of distinct videos containing those ad reads." + }, + "first_seen": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp of the earliest video with an ad read. Null when unknown." + }, + "last_seen": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp of the most recent video with an ad read. Null when unknown." + }, + "sponsor_status": { + "type": [ + "object", + "null" + ], + "properties": { + "status": { + "type": "string", + "description": "Curated sponsorship status from the known-advertisers dataset. Values: active (currently sponsoring), lapsed (no recent ad reads), ended (relationship known to have ended), uncertain (signal too weak to classify)." + }, + "ad_count": { + "type": [ + "integer", + "null" + ], + "description": "Curated ad count from the known-advertisers dataset." + }, + "first_ad_date": { + "type": [ + "string", + "null" + ], + "description": "Curated first-ad date. Null when not recorded." + }, + "last_ad_date": { + "type": [ + "string", + "null" + ], + "description": "Curated last-ad date. Null when not recorded." + } + }, + "required": [ + "status", + "ad_count", + "first_ad_date", + "last_ad_date" + ], + "description": "Curated known-advertiser record for this sponsor/channel pair. Null unless the pair exists in the curated dataset." + } + }, + "required": [ + "entity", + "ad_reads", + "videos", + "first_seen", + "last_seen", + "sponsor_status" + ] + }, + "TranscriptSearchResponse": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The q parameter echoed back." + }, + "requestedK": { + "type": "integer", + "description": "The limit applied." + }, + "returnedN": { + "type": "integer", + "description": "Chunks returned." + }, + "filters": { + "type": "object", + "properties": { + "channelIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Channel ids the search was scoped to, after entity_ids were expanded." + }, + "entityIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Exact explicit entity_ids accepted for this search. Every id was resolved; an unknown id is refused." + }, + "publishedAfter": { + "type": [ + "string", + "null" + ] + }, + "publishedBefore": { + "type": [ + "string", + "null" + ] + }, + "about": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamedEntityRef" + }, + "description": "The about ids, each with its name and type." + }, + "by": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamedEntityRef" + }, + "description": "The by ids, each with its name and type." + }, + "kind": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The kind values applied." + } + }, + "required": [ + "channelIds", + "entityIds", + "publishedAfter", + "publishedBefore", + "about", + "by", + "kind" + ] + }, + "chunks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TranscriptSearchChunk" + }, + "description": "Ranked slices. Empty means no hit in the shows we index; say so, never search the open web." + }, + "partial": { + "type": "boolean", + "description": "True when some retrieval batches failed and these chunks are what survived." + }, + "failedBatches": { + "type": "integer", + "description": "How many batches failed when partial is true." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "Newest publishedAt among the chunks. Null when there are none." + }, + "search_index": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "live", + "catching_up", + "unknown" + ], + "description": "Whether every indexed transcript is searchable. catching_up means older transcripts are still being added; note says so when the asked window reaches them." + }, + "missing_before": { + "type": [ + "string", + "null" + ], + "description": "While catching up, transcripts published before this date may be missing from search." + } + }, + "required": [ + "state", + "missing_before" + ], + "description": "Health of the search index behind these results. Catalog routes are unaffected by it." + }, + "access": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error" + ], + "description": "The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling." + }, + "code": { + "type": "string", + "description": "The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first." + }, + "reason": { + "type": "string", + "enum": [ + "no_credential", + "invalid", + "revoked" + ], + "description": "Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable." + }, + "message": { + "type": "string", + "description": "One plain line. Names the fix or the unlock." + }, + "param": { + "type": "string", + "description": "The query or body parameter the gate refused, when one did." + }, + "gate": { + "type": "string", + "enum": [ + "rows", + "key", + "plan", + "freshness", + "exposure_law", + "rate", + "pagination" + ], + "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." + }, + "unlock": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "The plan that lifts the gate." + }, + "url": { + "type": "string", + "description": "Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim." + }, + "offer": { + "type": "null", + "description": "Reserved for the agent-discount offer. Always null today." + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the call does. send_signup_code sends a verification code to an address for an account key." + }, + "method": { + "type": "string", + "description": "HTTP method to use." + }, + "url": { + "type": "string", + "description": "Absolute endpoint carrying its ?src= attribution. Call it verbatim." + } + }, + "required": [ + "kind", + "method", + "url" + ], + "description": "The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens." + } + }, + "required": [ + "tier", + "url", + "offer" + ], + "description": "How to lift the gate. Present when the gate has an unlock." + }, + "retry_after_seconds": { + "type": "integer", + "description": "Present on rate gates. Mirrors the Retry-After header." + }, + "doc_url": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "type", + "code", + "message", + "doc_url", + "request_id" + ], + "description": "The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would." + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this." + } + }, + "required": [ + "query", + "requestedK", + "returnedN", + "filters", + "chunks", + "as_of", + "search_index", + "note" + ], + "example": { + "query": "Ramp corporate cards", + "requestedK": 5, + "returnedN": 1, + "filters": { + "channelIds": [ + "UC-DRzaGnL_vtBUpCFH5M0tg" + ], + "entityIds": [], + "publishedAfter": null, + "publishedBefore": null, + "about": [ + { + "id": "ent_14", + "name": "Ramp", + "type": "organization" + } + ], + "by": [], + "kind": [ + "recommendation_sponsored" + ] + }, + "chunks": [ + { + "id": "UC-DRzaGnL_vtBUpCFH5M0tg/2026-08-04/dQw4w9WgXcQ.md#12", + "videoId": "dQw4w9WgXcQ", + "channelId": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channelName": "TBPN", + "videoTitle": "TBPN | Tuesday, August 4", + "speakers": [ + "John Coogan" + ], + "source": "creator_captions", + "sourceLabel": "Creator captions", + "publishedAt": "2026-08-04T17:00:00.000Z", + "text": "Ramp has been on the show for a while now and the pitch is still the same, spend less time on expenses.", + "startSeconds": 4787, + "watchUrl": "/watch?v=dQw4w9WgXcQ&t=4787", + "citeLine": "[TBPN | Tuesday, August 4](/watch?v=dQw4w9WgXcQ&t=4787) · @1:19:47 · TBPN · Aug 4, 2026", + "score": 0.71, + "about": [ + { + "id": "ent_14", + "name": "Ramp", + "type": "organization" + } + ], + "speakers_by": [ + { + "id": "ent_27", + "name": "John Coogan", + "type": "person" + } + ] + } + ], + "as_of": "2026-08-04T17:00:00.000Z", + "search_index": { + "state": "live", + "missing_before": null + }, + "note": "Quote text as a spoken beat of a few sentences and cite watchUrl with publishedAt. Results are recency-boosted; state the window from filters.publishedAfter when you passed one." + } + }, + "NamedEntityRef": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Public entity id, ent_{n}." + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "The entity name. Null when the id no longer resolves." + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified)." + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "TranscriptSearchChunk": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Search index chunk id. Opaque." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel." + }, + "channelName": { + "type": [ + "string", + "null" + ], + "description": "Source channel name." + }, + "channelPage": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + }, + "videoTitle": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "speakers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Speaker names identified on this slice, when known." + }, + "source": { + "type": [ + "string", + "null" + ], + "description": "Transcript source class: arcmira_premium, creator_captions, or third_party_quick." + }, + "sourceLabel": { + "type": [ + "string", + "null" + ], + "description": "Human label for source." + }, + "publishedAt": { + "type": [ + "string", + "null" + ], + "description": "Video publish timestamp. Cite it as the date of the quote." + }, + "text": { + "type": "string", + "description": "The spoken slice. Search results include text on every plan within the permitted publication-date window." + }, + "textWithheld": { + "type": "boolean", + "description": "Legacy field, no longer set. Since 2026-09-10, transcript search includes spoken text on every plan and limits results by publication date." + }, + "startSeconds": { + "type": [ + "integer", + "null" + ], + "description": "Offset of the slice in the video, in seconds." + }, + "watchUrl": { + "type": "string", + "description": "Site-relative watch URL with the timestamp, e.g. /watch?v=...&t=4787." + }, + "citeLine": { + "type": [ + "string", + "null" + ], + "description": "A ready citation line: title, clock, channel, date." + }, + "score": { + "type": "number", + "description": "Retrieval score. Higher is a closer match. Not comparable across calls." + }, + "about": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamedEntityRef" + }, + "description": "Entities the passage is tagged about (excerpt pins, exact-name mentions, ad verdicts). Present on passages served from the spoken index." + }, + "speakers_by": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamedEntityRef" + }, + "description": "The people speaking in the passage, as ids with names. Present on passages served from the spoken index." + } + }, + "required": [ + "id", + "videoId", + "channelId", + "source", + "publishedAt", + "text", + "startSeconds", + "watchUrl", + "score" + ] + }, + "EntityMomentumResponse": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/EntityRef" + }, + "verdict": { + "type": "string", + "enum": [ + "accelerating", + "flat", + "fading", + "none" + ], + "description": "Absolute delta of the last 30 days against the prior 30. none when the entity has no mentions at all." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "Newest indexed media that mentions the entity. Lead with it; it is the date the verdict is true as of." + }, + "coverage": { + "type": "string", + "description": "What the count measures. Always the shows we index, never the whole internet." + }, + "volume": { + "type": "object", + "properties": { + "mentions_7d": { + "type": "integer" + }, + "mentions_30d": { + "type": "integer" + }, + "mentions_prior_30d": { + "type": "integer" + }, + "delta_30d_absolute": { + "type": "integer" + }, + "delta_30d_pct": { + "type": [ + "number", + "null" + ], + "description": "Percent change against the prior 30 days. Null when the prior window was zero." + }, + "mentions_90d": { + "type": "integer" + }, + "total": { + "type": "integer", + "description": "All-time mentions in the index." + } + }, + "required": [ + "mentions_7d", + "mentions_30d", + "mentions_prior_30d", + "delta_30d_absolute", + "delta_30d_pct", + "mentions_90d", + "total" + ] + }, + "top_shows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": [ + "string", + "null" + ] + }, + "channel_name": { + "type": [ + "string", + "null" + ] + }, + "channel_page": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + }, + "mentions": { + "type": "integer" + } + }, + "required": [ + "channel_id", + "channel_name", + "channel_page", + "mentions" + ] + }, + "description": "Up to five channels by mentions in the last 30 days." + }, + "paid_vs_organic": { + "type": "object", + "properties": { + "ad_reads": { + "type": "integer" + }, + "endorsements": { + "type": "integer" + }, + "organic": { + "type": "integer" + } + }, + "required": [ + "ad_reads", + "endorsements", + "organic" + ], + "description": "Commercial split for the last 30 days. Present only on a Pro+ plan; otherwise access names the gate." + }, + "access": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error" + ], + "description": "The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling." + }, + "code": { + "type": "string", + "description": "The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first." + }, + "reason": { + "type": "string", + "enum": [ + "no_credential", + "invalid", + "revoked" + ], + "description": "Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable." + }, + "message": { + "type": "string", + "description": "One plain line. Names the fix or the unlock." + }, + "param": { + "type": "string", + "description": "The query or body parameter the gate refused, when one did." + }, + "gate": { + "type": "string", + "enum": [ + "rows", + "key", + "plan", + "freshness", + "exposure_law", + "rate", + "pagination" + ], + "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." + }, + "unlock": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "The plan that lifts the gate." + }, + "url": { + "type": "string", + "description": "Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim." + }, + "offer": { + "type": "null", + "description": "Reserved for the agent-discount offer. Always null today." + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the call does. send_signup_code sends a verification code to an address for an account key." + }, + "method": { + "type": "string", + "description": "HTTP method to use." + }, + "url": { + "type": "string", + "description": "Absolute endpoint carrying its ?src= attribution. Call it verbatim." + } + }, + "required": [ + "kind", + "method", + "url" + ], + "description": "The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens." + } + }, + "required": [ + "tier", + "url", + "offer" + ], + "description": "How to lift the gate. Present when the gate has an unlock." + }, + "retry_after_seconds": { + "type": "integer", + "description": "Present on rate gates. Mirrors the Retry-After header." + }, + "doc_url": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "type", + "code", + "message", + "doc_url", + "request_id" + ], + "description": "The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would." + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this." + } + }, + "required": [ + "entity", + "verdict", + "as_of", + "coverage", + "volume", + "top_shows", + "note" + ], + "example": { + "entity": { + "id": "ent_14", + "name": "Ramp", + "type": "organization", + "slug": "ramp", + "page": "https://arcmira.com/org/ramp" + }, + "verdict": "accelerating", + "as_of": "2026-08-28T17:00:00.000Z", + "coverage": "Shows we index, not the whole internet.", + "volume": { + "mentions_7d": 6, + "mentions_30d": 23, + "mentions_prior_30d": 11, + "delta_30d_absolute": 12, + "delta_30d_pct": 109.1, + "mentions_90d": 41, + "total": 188 + }, + "top_shows": [ + { + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "channel_page": "https://arcmira.com/yt/@TBPNLive", + "mentions": 17 + }, + { + "channel_id": "UClWkDGXEzsh77GAhs90wpXw", + "channel_name": "Moment of Truth", + "channel_page": "https://arcmira.com/yt/@MomentofTruthShow", + "mentions": 3 + } + ], + "access": { + "type": "permission_error", + "code": "recommendations_not_enabled", + "message": "The paid versus organic split requires Pro+. Open unlock.url to start Pro+.", + "gate": "plan", + "resource": { + "kind": "commercial", + "what": "paid_split" + }, + "unlock": { + "tier": "pro_plus", + "url": "https://arcmira.com/pricing?src=mcp-tool", + "offer": null + }, + "doc_url": "https://arcmira.com/docs/errors#recommendations_not_enabled", + "request_id": "req_7f3c2a" + }, + "note": "Lead with verdict and as_of. Then volume and shows. The paid vs organic split needs a plan that carries it; card.access says so. Search transcripts second for quotes. Never invent a heat score." + } + }, + "ChannelCoverageResponse": { + "type": "object", + "properties": { + "channel": { + "type": "object", + "properties": { + "youtube_channel_id": { + "type": "string", + "description": "The channel id as supplied." + }, + "searchable_videos": { + "type": "integer", + "description": "Videos with a completed transcript. 0 means we do not cover the channel." + }, + "indexed_through": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date among those videos. Mentions, entity lookups and transcripts read through here. Null when nothing is indexed." + }, + "search_indexed_through": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date transcript search can hit. New transcripts are searchable when they are indexed, so it equals indexed_through." + }, + "source_mix": { + "type": "object", + "properties": { + "arcmira_premium": { + "type": "integer" + }, + "creator_captions": { + "type": "integer" + }, + "third_party_quick": { + "type": "integer" + } + }, + "required": [ + "arcmira_premium", + "creator_captions", + "third_party_quick" + ], + "description": "searchable_videos split by transcript source class." + } + }, + "required": [ + "youtube_channel_id", + "searchable_videos", + "indexed_through", + "search_indexed_through", + "source_mix" + ] + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this." + } + }, + "required": [ + "channel", + "note" + ], + "example": { + "channel": { + "youtube_channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "searchable_videos": 412, + "indexed_through": "2026-08-28T17:00:00.000Z", + "search_indexed_through": "2026-08-28T17:00:00.000Z", + "source_mix": { + "arcmira_premium": 96, + "creator_captions": 301, + "third_party_quick": 15 + } + }, + "note": "Cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. An empty search is a miss up to search_indexed_through, not proof it was never said." + } + }, + "ChannelVideosResponse": { + "type": "object", + "properties": { + "channel": { + "type": "object", + "properties": { + "id": { + "type": [ + "string", + "null" + ], + "description": "Public entity id (\"ent_{n}\") of the channel. Null when no channel entity is known." + }, + "youtube_channel_id": { + "type": "string", + "description": "The channel id as supplied." + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + } + }, + "required": [ + "id", + "youtube_channel_id", + "name", + "page" + ] + }, + "episodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "video_id": { + "type": "string", + "description": "11-character YouTube video id. Pass it to GET /v1/mentions/counts video_ids or GET /v1/transcripts/{video_id}." + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "published_at": { + "type": [ + "string", + "null" + ], + "description": "ISO publish date on YouTube." + }, + "duration_seconds": { + "type": [ + "integer", + "null" + ], + "description": "Video length in seconds. Null when YouTube reported none." + }, + "view_count": { + "type": [ + "integer", + "null" + ] + }, + "channel_id": { + "type": "string", + "description": "The channel id as supplied." + }, + "channel_name": { + "type": [ + "string", + "null" + ] + }, + "channel_page": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + }, + "watch_url": { + "type": "string", + "description": "The episode on arcmira.com, absolute." + } + }, + "required": [ + "video_id", + "title", + "published_at", + "duration_seconds", + "view_count", + "channel_id", + "channel_name", + "channel_page", + "watch_url" + ] + }, + "description": "Indexed videos of the channel, newest first." + }, + "returned": { + "type": "integer" + }, + "has_more": { + "type": "boolean", + "description": "True when more indexed videos exist past limit in the window." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Signed continuation for the next page. Null on the last page." + }, + "indexed_through": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date among every indexed video of the channel, whatever window was asked for. Null when nothing is indexed." + }, + "index_age_days": { + "type": [ + "integer", + "null" + ], + "description": "Whole days between indexed_through and now. Past 30 the note says the index may be behind the channel. Null when nothing is indexed." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "Same as indexed_through." + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this." + } + }, + "required": [ + "channel", + "episodes", + "returned", + "has_more", + "next_cursor", + "indexed_through", + "index_age_days", + "as_of", + "note" + ], + "example": { + "channel": { + "id": "ent_566342", + "youtube_channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "name": "TBPN", + "page": "https://arcmira.com/yt/@TBPNLive" + }, + "episodes": [ + { + "video_id": "dQw4w9WgXcQ", + "title": "TBPN | Friday, August 28", + "published_at": "2026-08-28T17:00:00.000Z", + "duration_seconds": 10812, + "view_count": 48210, + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "channel_page": "https://arcmira.com/yt/@TBPNLive", + "watch_url": "https://arcmira.com/watch?v=dQw4w9WgXcQ" + } + ], + "returned": 1, + "has_more": true, + "indexed_through": "2026-08-28T17:00:00.000Z", + "index_age_days": 3, + "as_of": "2026-08-28T17:00:00.000Z", + "note": "Newest indexed episodes first. Pass a video_id to count_occurrences videoIds to list what one episode mentions, or to get_transcript to read it." + } + }, + "MentionCountsResponse": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "mentions", + "appearances", + "both" + ], + "description": "The mode applied." + }, + "publishedAfter": { + "type": [ + "string", + "null" + ] + }, + "publishedBefore": { + "type": [ + "string", + "null" + ] + }, + "channelIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The channel ids counted." + }, + "videoIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The video ids the count was scoped to. Empty when it was not." + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "description": "Public entity id (\"ent_{n}\")." + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for." + }, + "appearances_page": { + "type": [ + "string", + "null" + ], + "description": "For a person, the list of their appearances on arcmira.com (their page opens on it). Null for every other type." + }, + "mentions_page": { + "type": [ + "string", + "null" + ], + "description": "For a person, the list of mentions of them on arcmira.com, page/mentions. Null for every other type: their page is the mentions list already." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug. Null when never slugged." + }, + "channel_id": { + "type": [ + "string", + "null" + ] + }, + "channel_name": { + "type": [ + "string", + "null" + ] + }, + "channel_page": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + }, + "count": { + "type": "integer", + "description": "Episodes the entity came up in." + }, + "occurrences": { + "type": "integer", + "description": "Times the entity came up across those episodes (mentions or appearances per mode)." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "Newest media in this count." + } + }, + "required": [ + "entity_id", + "name", + "type", + "page", + "appearances_page", + "mentions_page", + "slug", + "channel_id", + "channel_name", + "channel_page", + "count", + "occurrences", + "as_of" + ] + }, + "description": "One row per entity and channel, ranked by count." + }, + "returned": { + "type": "integer" + }, + "has_more": { + "type": "boolean", + "description": "True when more entity and channel pairs exist past limit." + }, + "shared": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entity_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "page": { + "type": [ + "string", + "null" + ], + "description": "The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for." + }, + "appearances_page": { + "type": [ + "string", + "null" + ], + "description": "For a person, their appearances list. Null otherwise." + }, + "mentions_page": { + "type": [ + "string", + "null" + ], + "description": "For a person, the mentions of them. Null otherwise." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "URL slug. Null when never slugged." + }, + "channel_count": { + "type": "integer", + "description": "How many of the requested channels carry this entity." + }, + "by_channel": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id." + }, + "channel_name": { + "type": [ + "string", + "null" + ] + }, + "channel_page": { + "type": [ + "string", + "null" + ], + "description": "The channel's page on arcmira.com, absolute. Null when no channel is known." + }, + "count": { + "type": "integer", + "description": "Episodes the entity came up in." + }, + "occurrences": { + "type": "integer", + "description": "Times the entity came up across those episodes (mentions or appearances per mode)." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "Newest media in this count." + } + }, + "required": [ + "channel_id", + "channel_name", + "channel_page", + "count", + "occurrences", + "as_of" + ] + } + } + }, + "required": [ + "entity_id", + "name", + "type", + "page", + "appearances_page", + "mentions_page", + "slug", + "channel_count", + "by_channel" + ] + }, + "description": "Entities on two or more of the requested channels, ranked by the smallest per-channel count. Empty for one channel." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "Newest media across rows. Null when there are none." + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this." + } + }, + "required": [ + "mode", + "publishedAfter", + "publishedBefore", + "channelIds", + "videoIds", + "rows", + "returned", + "has_more", + "shared", + "as_of", + "note" + ], + "example": { + "mode": "mentions", + "publishedAfter": "2026-06-01", + "publishedBefore": null, + "channelIds": [ + "UC-DRzaGnL_vtBUpCFH5M0tg", + "UClWkDGXEzsh77GAhs90wpXw" + ], + "videoIds": [], + "rows": [ + { + "entity_id": "ent_14", + "name": "Ramp", + "type": "organization", + "page": "https://arcmira.com/org/ramp", + "appearances_page": null, + "mentions_page": null, + "slug": "ramp", + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "channel_page": "https://arcmira.com/yt/@TBPNLive", + "count": 23, + "as_of": "2026-08-28T17:00:00.000Z" + }, + { + "entity_id": "ent_14", + "name": "Ramp", + "type": "organization", + "page": "https://arcmira.com/org/ramp", + "appearances_page": null, + "mentions_page": null, + "slug": "ramp", + "channel_id": "UClWkDGXEzsh77GAhs90wpXw", + "channel_name": "Moment of Truth", + "channel_page": "https://arcmira.com/yt/@MomentofTruthShow", + "count": 3, + "occurrences": 7, + "as_of": "2026-08-21T16:00:00.000Z" + } + ], + "returned": 2, + "has_more": false, + "shared": [ + { + "entity_id": "ent_14", + "name": "Ramp", + "type": "organization", + "page": "https://arcmira.com/org/ramp", + "appearances_page": null, + "mentions_page": null, + "slug": "ramp", + "channel_count": 2, + "by_channel": [ + { + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "channel_page": "https://arcmira.com/yt/@TBPNLive", + "count": 23, + "as_of": "2026-08-28T17:00:00.000Z" + }, + { + "channel_id": "UClWkDGXEzsh77GAhs90wpXw", + "channel_name": "Moment of Truth", + "channel_page": "https://arcmira.com/yt/@MomentofTruthShow", + "count": 3, + "occurrences": 7, + "as_of": "2026-08-21T16:00:00.000Z" + } + ] + } + ], + "as_of": "2026-08-28T17:00:00.000Z", + "note": "Catalog counts, all-time unless you passed a window. shared ranks true overlap by the smallest per-channel count. Use search_transcripts afterwards only for quotes." + } + }, + "PersonPageResponse": { + "type": "object", + "properties": { + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Person name." + }, + "type": { + "type": "string", + "description": "Stored entity type. Always person." + }, + "platform": { + "type": [ + "string", + "null" + ], + "description": "Source platform. Null for people." + }, + "url": { + "type": [ + "string", + "null" + ], + "description": "External URL for the person. Null when none is known." + }, + "created_at": { + "type": "string", + "description": "When the entity row was created." + }, + "image_url": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until resolved." + }, + "image_checked_at": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "merged_into_entity_id": { + "type": [ + "integer", + "null" + ], + "description": "Raw id of the entity this one was merged into. Null on a canonical entity, which a page always serves." + }, + "owner_entity_id": { + "type": [ + "integer", + "null" + ], + "description": "Raw id of the owning entity. Null unless an ownership link exists." + }, + "youtube_channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id. Null for people." + }, + "youtube_handle": { + "type": [ + "string", + "null" + ], + "description": "YouTube handle. Null for people." + }, + "is_priority": { + "type": [ + "integer", + "null" + ], + "description": "1 when the entity is flagged priority, 0 or null otherwise." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Same value as image_url." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "Same value as image_checked_at." + }, + "ownedChannels": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Entity name." + }, + "display_name": { + "type": "string", + "description": "Label that tells a homonym apart, e.g. \"AdQuick (channel)\". Equals name when no label is needed." + }, + "type": { + "type": "string", + "description": "Entity type: channel or product." + }, + "route": { + "type": "string", + "description": "Site-relative route of the entity page on arcmira.com." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Entity image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "videoCount": { + "type": "integer", + "description": "Channels only: media rows published by the channel." + }, + "mentionCount": { + "type": "integer", + "description": "Products only: appearance rows of the product." + } + }, + "required": [ + "id", + "name", + "display_name", + "type", + "route", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Up to 10 channels this entity owns, most videos first. Null when it owns none." + }, + "ownedProducts": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Entity name." + }, + "display_name": { + "type": "string", + "description": "Label that tells a homonym apart, e.g. \"AdQuick (channel)\". Equals name when no label is needed." + }, + "type": { + "type": "string", + "description": "Entity type: channel or product." + }, + "route": { + "type": "string", + "description": "Site-relative route of the entity page on arcmira.com." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Entity image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "videoCount": { + "type": "integer", + "description": "Channels only: media rows published by the channel." + }, + "mentionCount": { + "type": "integer", + "description": "Products only: appearance rows of the product." + } + }, + "required": [ + "id", + "name", + "display_name", + "type", + "route", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Up to 10 products this entity owns, most mentions first. Null when it owns none." + } + }, + "required": [ + "id", + "name", + "type", + "platform", + "url", + "created_at", + "image_url", + "image_checked_at", + "merged_into_entity_id", + "owner_entity_id", + "youtube_channel_id", + "youtube_handle", + "is_priority", + "imageUrl", + "imageCheckedAt", + "ownedChannels", + "ownedProducts" + ], + "description": "The person: the stored entity row plus camelCase image fields and what the person owns." + }, + "roleEdge": { + "type": [ + "object", + "null" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "ceo" + ], + "description": "Always ceo." + }, + "label": { + "type": "string", + "enum": [ + "CEO of", + "CO-CEO of" + ], + "description": "Display label for the role line." + }, + "name": { + "type": "string", + "description": "Name of the organization the person leads." + }, + "href": { + "type": [ + "string", + "null" + ], + "description": "Site-relative link to the organization page. Null when none." + }, + "receipt": { + "type": [ + "string", + "null" + ], + "description": "Display line naming the evidence for the role. Null when there is none to show." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Organization logo URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked the organization. Null until checked." + } + }, + "required": [ + "role", + "label", + "name", + "href", + "receipt", + "imageUrl", + "imageCheckedAt" + ], + "description": "A verified CEO role for the person. Null when none is verified." + }, + "stats": { + "type": "object", + "properties": { + "velocity": { + "type": "integer", + "description": "Appearances in the last 90 days." + }, + "sentiment": { + "type": "number", + "description": "Reserved. Always 0." + }, + "reach": { + "type": "string", + "description": "Total views of the person's appearances as display text, e.g. 1.2M." + }, + "reachRaw": { + "type": "number", + "description": "Total views of the person's appearances." + }, + "total": { + "type": "integer", + "description": "Total appearances." + }, + "totalMentions": { + "type": "integer", + "description": "Total media that mention the person." + }, + "totalMentionViews": { + "type": "number", + "description": "Total views of the media that mention the person." + }, + "mentionReach": { + "type": "string", + "description": "totalMentionViews as display text." + }, + "latestMediaAt": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date among the counted media. Null when none. The freshness gate does not withhold it." + } + }, + "required": [ + "velocity", + "sentiment", + "reach", + "reachRaw", + "total", + "totalMentions", + "totalMentionViews", + "mentionReach", + "latestMediaAt" + ], + "description": "Headline numbers for the person." + }, + "appearancesByMonth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "Three-letter month name, e.g. Jan." + }, + "yearMonth": { + "type": "string", + "description": "The month as YYYY-MM." + }, + "count": { + "type": "integer", + "description": "Media that month. Months inside the withheld window read 0." + } + }, + "required": [ + "month", + "yearMonth", + "count" + ] + }, + "description": "Appearances per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart." + }, + "mentionsByMonth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "Three-letter month name, e.g. Jan." + }, + "yearMonth": { + "type": "string", + "description": "The month as YYYY-MM." + }, + "count": { + "type": "integer", + "description": "Media that month. Months inside the withheld window read 0." + } + }, + "required": [ + "month", + "yearMonth", + "count" + ] + }, + "description": "Media mentioning the person per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart." + }, + "topics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Topic name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + } + }, + "required": [ + "name", + "count", + "sentiment" + ] + }, + "description": "Topics in the media the person appeared in, highest count first." + }, + "people": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "role": { + "type": "string", + "enum": [ + "Connection" + ], + "description": "Always Connection." + } + }, + "required": [ + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt", + "role" + ] + }, + "description": "People in the media the person appeared in, highest count first." + }, + "brands": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Organizations in the media the person appeared in, highest count first." + }, + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Products in the media the person appeared in, highest count first." + }, + "appearances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Raw appearance row id (the first row for the video), as a string." + }, + "date": { + "type": "string", + "description": "Publish date as locale display text, or Unknown." + }, + "publishedAt": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Null when unknown." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "channel": { + "type": "string", + "description": "Source channel name, or Unknown Channel." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel. Null when unknown." + }, + "channelHandle": { + "type": [ + "string", + "null" + ], + "description": "YouTube handle of the source channel. Null when unknown." + }, + "platform": { + "type": "string", + "enum": [ + "youtube" + ], + "description": "Always youtube." + }, + "thumbnail": { + "type": "string", + "description": "Video thumbnail URL." + }, + "thumbnailUrl": { + "type": "string", + "description": "Video thumbnail URL. Same value as thumbnail." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "duration": { + "type": [ + "number", + "null" + ], + "description": "Video length in seconds. Null when unknown." + }, + "type": { + "type": "string", + "enum": [ + "host", + "guest", + "mention" + ], + "description": "host or guest on an appearance row (host when the person hosts the channel); mention on a mention row." + }, + "context": { + "type": "string", + "description": "Description of the moment, or \"No description available\"." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "timestamp": { + "type": "string", + "description": "Earliest start timestamp as MM:SS text, or \"Full Episode\" when none." + }, + "rawTimestamp": { + "type": [ + "string", + "null" + ], + "description": "Earliest start timestamp as stored. Null when none." + }, + "excerpt": { + "$ref": "#/components/schemas/PublishedExcerpt" + } + }, + "required": [ + "id", + "date", + "publishedAt", + "title", + "channel", + "channelId", + "channelHandle", + "platform", + "thumbnail", + "thumbnailUrl", + "videoId", + "duration", + "type", + "context", + "sentiment", + "timestamp", + "rawTimestamp" + ] + }, + "description": "Newest media the person appeared in, one row per video, cut to the plan's media rows." + }, + "mentions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Raw appearance row id (the first row for the video), as a string." + }, + "date": { + "type": "string", + "description": "Publish date as locale display text, or Unknown." + }, + "publishedAt": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Null when unknown." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "channel": { + "type": "string", + "description": "Source channel name, or Unknown Channel." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel. Null when unknown." + }, + "channelHandle": { + "type": [ + "string", + "null" + ], + "description": "YouTube handle of the source channel. Null when unknown." + }, + "platform": { + "type": "string", + "enum": [ + "youtube" + ], + "description": "Always youtube." + }, + "thumbnail": { + "type": "string", + "description": "Video thumbnail URL." + }, + "thumbnailUrl": { + "type": "string", + "description": "Video thumbnail URL. Same value as thumbnail." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "duration": { + "type": [ + "number", + "null" + ], + "description": "Video length in seconds. Null when unknown." + }, + "type": { + "type": "string", + "enum": [ + "host", + "guest", + "mention" + ], + "description": "host or guest on an appearance row (host when the person hosts the channel); mention on a mention row." + }, + "context": { + "type": "string", + "description": "Description of the moment, or \"No description available\"." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "timestamp": { + "type": "string", + "description": "Earliest start timestamp as MM:SS text, or \"Full Episode\" when none." + }, + "rawTimestamp": { + "type": [ + "string", + "null" + ], + "description": "Earliest start timestamp as stored. Null when none." + }, + "excerpt": { + "$ref": "#/components/schemas/PublishedExcerpt" + } + }, + "required": [ + "id", + "date", + "publishedAt", + "title", + "channel", + "channelId", + "channelHandle", + "platform", + "thumbnail", + "thumbnailUrl", + "videoId", + "duration", + "type", + "context", + "sentiment", + "timestamp", + "rawTimestamp" + ] + }, + "description": "Newest media that mention the person without them appearing, one row per video." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "entity", + "roleEdge", + "stats", + "appearancesByMonth", + "mentionsByMonth", + "topics", + "people", + "brands", + "products", + "appearances", + "mentions", + "_meta" + ] + }, + "PublishedExcerpt": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Published excerpt id." + }, + "exactText": { + "type": "string", + "description": "The transcript span that names the entity." + }, + "contextBefore": { + "type": "string", + "description": "Transcript text immediately before the span." + }, + "contextAfter": { + "type": "string", + "description": "Transcript text immediately after the span." + }, + "term": { + "type": "string", + "description": "The surface form that matched in the transcript, which may be an approved alias." + }, + "termCharStart": { + "type": "integer", + "description": "Character offset where term starts." + }, + "termCharEnd": { + "type": "integer", + "description": "Character offset where term ends." + }, + "startSeconds": { + "type": "number", + "description": "Span start in the video, in seconds." + }, + "endSeconds": { + "type": "number", + "description": "Span end in the video, in seconds." + }, + "publicSourceClass": { + "type": "string", + "enum": [ + "arcmira_premium_excerpt", + "creator_captions", + "third_party_quick" + ], + "description": "Transcript source class of the excerpt." + } + }, + "required": [ + "id", + "exactText", + "term", + "termCharStart", + "termCharEnd", + "startSeconds", + "publicSourceClass" + ], + "description": "A speakerless excerpt naming the entity in this media, when one is active. Mention rows only." + }, + "ExposureMeta": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "Plan tier of the caller. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise." + }, + "isAuthenticated": { + "type": "boolean", + "description": "True when the request carried a valid credential." + }, + "rowsUsed": { + "type": "number", + "description": "Rows consumed this period, or lifetime rows on the free tier." + }, + "rowsRemaining": { + "type": "number", + "description": "Rows left this period, or lifetime rows left on the free tier." + }, + "monthlyRows": { + "type": "number", + "description": "Rows included per period, or the lifetime allocation on the free tier." + }, + "isOverLimit": { + "type": "boolean", + "description": "True when rowsUsed has reached monthlyRows." + }, + "onDemandEnabled": { + "type": "boolean", + "description": "True when on-demand usage past the included rows is enabled." + }, + "spendLimitCents": { + "type": "number", + "description": "On-demand spend limit in US cents. 0 means unlimited." + }, + "currentSpendCents": { + "type": "number", + "description": "On-demand spend so far this period, in US cents." + }, + "canContinue": { + "type": "boolean", + "description": "True when the caller can keep reading data right now." + }, + "showingFullData": { + "type": "boolean", + "description": "True when the plan serves full data. False when the response is a preview: counts are null and lists are cut." + }, + "usageLimitType": { + "type": "string", + "enum": [ + "lifetime", + "monthly" + ], + "description": "lifetime on the free tier, whose rows never reset. monthly on paid tiers." + }, + "lifetimeRowsUsed": { + "type": "number", + "description": "Free tier only: lifetime rows used. Absent on paid tiers." + }, + "lifetimeRowsAllocated": { + "type": "number", + "description": "Free tier only: lifetime rows allocated. Absent on paid tiers." + }, + "limitAction": { + "type": "string", + "enum": [ + "upgrade_to_pro", + "upgrade_or_enable_ondemand", + "upgrade_or_increase_limit", + "enable_ondemand", + "increase_limit", + "contact_sales" + ], + "description": "Present only when the caller is over the limit and cannot continue. Names the fix: upgrade_to_pro, upgrade_or_enable_ondemand, upgrade_or_increase_limit, enable_ondemand, increase_limit, or contact_sales." + }, + "limitMessage": { + "type": "string", + "description": "Human sentence for the limit banner. Present only alongside limitAction." + }, + "limitCtaHref": { + "type": "string", + "description": "Site path for the limit banner button. Present only alongside limitAction." + }, + "limitUpgradeTier": { + "type": "string", + "description": "The next tier that lifts the limit. Present only alongside limitAction when an upgrade exists. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise." + }, + "limitUpgradeTierName": { + "type": "string", + "description": "Display name of limitUpgradeTier." + }, + "credits": { + "type": "object", + "properties": { + "available": { + "type": [ + "integer", + "null" + ], + "description": "Credits spendable now: plan, granted, purchased, and on-demand up to its cap. Null when nothing limits it." + }, + "plan": { + "type": "object", + "properties": { + "credits": { + "type": [ + "integer", + "null" + ], + "description": "Plan credits this month. Null when the plan has no limit." + }, + "used": { + "type": "integer", + "description": "Plan credits spent this month." + }, + "resets_at": { + "type": "string", + "description": "YYYY-MM-DD, the first day of next month (UTC), when plan credits reset." + } + }, + "required": [ + "credits", + "used", + "resets_at" + ] + }, + "granted": { + "type": "integer", + "description": "Credits left in granted lots that have not expired." + }, + "purchased": { + "type": "integer", + "description": "Credits left in purchased top-ups." + }, + "on_demand": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "True when on-demand credits are on." + }, + "cap_credits": { + "type": [ + "integer", + "null" + ], + "description": "The on-demand cap in credits, at $0.002 a credit. Null when uncapped or off." + }, + "used": { + "type": "integer", + "description": "On-demand credits spent this month." + } + }, + "required": [ + "enabled", + "cap_credits", + "used" + ] + } + }, + "required": [ + "available", + "plan", + "granted", + "purchased", + "on_demand" + ], + "description": "The month in credits (1 credit is $0.001; a row is 4 credits). Present only when the credits ledger decides access." + }, + "totals": { + "type": "object", + "properties": { + "appearances": { + "type": "integer", + "description": "Total media rows: appearances for a person, mentions for other types, episodes for a channel." + }, + "mentions": { + "type": "integer", + "description": "Person pages only: total media that mention the person." + }, + "topics": { + "type": "integer", + "description": "Total related topics." + }, + "people": { + "type": "integer", + "description": "Total related people." + }, + "brands": { + "type": "integer", + "description": "Total related organizations, under the legacy key brands." + }, + "products": { + "type": "integer", + "description": "Total related products." + }, + "channels": { + "type": "integer", + "description": "Total related channels." + }, + "organizations": { + "type": "integer", + "description": "Total related organizations (organizations list)." + }, + "guests": { + "type": "integer", + "description": "Channel only: total unique guests." + }, + "hosts": { + "type": "integer", + "description": "Channel only: total hosts." + } + }, + "description": "True totals behind the returned rows. Each response sets only the keys for its own sections." + }, + "limits": { + "type": "object", + "properties": { + "freeAppearances": { + "type": "integer", + "description": "Media rows an anonymous caller is served." + }, + "freeEntitiesPerType": { + "type": "integer", + "description": "Sidebar rows per entity type an anonymous caller is served." + }, + "webhooksEnabled": { + "type": "boolean", + "description": "True when the plan includes webhook delivery." + }, + "exportEnabled": { + "type": "boolean", + "description": "True when the plan includes export." + }, + "apiEnabled": { + "type": "boolean", + "description": "True when the plan includes API access." + } + }, + "required": [ + "freeAppearances", + "freeEntitiesPerType", + "webhooksEnabled", + "exportEnabled", + "apiEnabled" + ], + "description": "Anonymous row limits and the plan's feature flags." + }, + "costPerRowCents": { + "type": "number", + "description": "On-demand price per row in US cents, e.g. 0.4. 0 on tiers without on-demand." + }, + "isOverage": { + "type": "boolean", + "description": "True when this request billed rows past the included allowance." + }, + "freeLimit": { + "type": "object", + "properties": { + "appearances": { + "type": "integer", + "description": "Same as limits.freeAppearances." + }, + "entities": { + "type": "integer", + "description": "Same as limits.freeEntitiesPerType." + } + }, + "required": [ + "appearances", + "entities" + ], + "description": "The anonymous row limits again, under their older key." + }, + "recentPreview": { + "type": "object", + "properties": { + "windowDays": { + "type": "integer", + "description": "Days of newest media the plan withholds." + }, + "hiddenRecentCount": { + "type": "integer", + "description": "Media rows inside the withheld window." + }, + "freeAccountUnlockCount": { + "type": "integer", + "description": "Rows a free account would unlock. Present only for anonymous callers (freshness code 2)." + }, + "newestHiddenAt": { + "type": "string", + "description": "Newest publish date inside the withheld window. Absent when nothing is withheld." + }, + "teaserItems": { + "type": "array", + "items": { + "type": "object", + "properties": { + "thumbnailUrl": { + "type": "string", + "description": "Video thumbnail URL." + }, + "channelName": { + "type": "string", + "description": "Source channel name." + }, + "publishedAt": { + "type": "string", + "description": "Publish date of the withheld video." + }, + "duration": { + "type": [ + "string", + "null" + ], + "description": "Video length as display text. Null when unknown." + }, + "title": { + "type": "string", + "description": "Video title, truncated server side." + } + }, + "required": [ + "thumbnailUrl", + "channelName", + "publishedAt", + "duration", + "title" + ] + }, + "description": "Up to three of the newest withheld rows, with safe fields only." + }, + "subject": { + "type": "string", + "enum": [ + "appearances", + "mentions", + "items" + ], + "description": "Which list the withheld rows belong to." + }, + "experiment": { + "type": "string", + "enum": [ + "recent-intel-gate-copy-v1" + ], + "description": "The copy experiment this band feeds. Always recent-intel-gate-copy-v1." + } + }, + "required": [ + "windowDays", + "hiddenRecentCount", + "subject", + "experiment" + ], + "description": "Present when the plan withholds the newest media: how much is hidden and a few safe teaser rows." + }, + "recentPreviewMentions": { + "type": "object", + "properties": { + "windowDays": { + "type": "integer", + "description": "Days of newest media the plan withholds." + }, + "hiddenRecentCount": { + "type": "integer", + "description": "Media rows inside the withheld window." + }, + "freeAccountUnlockCount": { + "type": "integer", + "description": "Rows a free account would unlock. Present only for anonymous callers (freshness code 2)." + }, + "newestHiddenAt": { + "type": "string", + "description": "Newest publish date inside the withheld window. Absent when nothing is withheld." + }, + "teaserItems": { + "type": "array", + "items": { + "type": "object", + "properties": { + "thumbnailUrl": { + "type": "string", + "description": "Video thumbnail URL." + }, + "channelName": { + "type": "string", + "description": "Source channel name." + }, + "publishedAt": { + "type": "string", + "description": "Publish date of the withheld video." + }, + "duration": { + "type": [ + "string", + "null" + ], + "description": "Video length as display text. Null when unknown." + }, + "title": { + "type": "string", + "description": "Video title, truncated server side." + } + }, + "required": [ + "thumbnailUrl", + "channelName", + "publishedAt", + "duration", + "title" + ] + }, + "description": "Up to three of the newest withheld rows, with safe fields only." + }, + "subject": { + "type": "string", + "enum": [ + "appearances", + "mentions", + "items" + ], + "description": "Which list the withheld rows belong to." + }, + "experiment": { + "type": "string", + "enum": [ + "recent-intel-gate-copy-v1" + ], + "description": "The copy experiment this band feeds. Always recent-intel-gate-copy-v1." + } + }, + "required": [ + "windowDays", + "hiddenRecentCount", + "subject", + "experiment" + ], + "description": "Person pages only: the same band for the mentions list." + }, + "access": { + "type": "object", + "properties": { + "v": { + "type": "number", + "enum": [ + 1 + ], + "description": "Access block version. Always 1." + }, + "view": { + "type": "string", + "enum": [ + "full", + "preview" + ], + "description": "full when the plan serves everything; preview when something was cut." + }, + "cls": { + "type": "string", + "enum": [ + "live", + "warm", + "public", + "cold" + ], + "description": "Freshness class of the response. live serves the newest media; the others delay it." + }, + "ladder": { + "type": "string", + "enum": [ + "anonymous", + "free", + "usage_limit", + "unlocked" + ], + "description": "Where the caller stands on the gate ladder." + }, + "rows": { + "type": "object", + "properties": { + "media": { + "type": "object", + "properties": { + "served": { + "type": "integer", + "description": "Rows the backend returns for this section." + }, + "visible": { + "type": "integer", + "description": "Rows the site paints unobscured." + }, + "placeholders": { + "type": "integer", + "description": "Blurred rows the site paints after the visible ones." + } + }, + "required": [ + "served", + "visible", + "placeholders" + ], + "description": "Media rows." + }, + "topics": { + "type": "object", + "properties": { + "served": { + "type": "integer", + "description": "Rows the backend returns for this section." + }, + "visible": { + "type": "integer", + "description": "Rows the site paints unobscured." + }, + "placeholders": { + "type": "integer", + "description": "Blurred rows the site paints after the visible ones." + } + }, + "required": [ + "served", + "visible", + "placeholders" + ], + "description": "Topic sidebar rows." + }, + "entities": { + "type": "object", + "properties": { + "served": { + "type": "integer", + "description": "Rows the backend returns for this section." + }, + "visible": { + "type": "integer", + "description": "Rows the site paints unobscured." + }, + "placeholders": { + "type": "integer", + "description": "Blurred rows the site paints after the visible ones." + } + }, + "required": [ + "served", + "visible", + "placeholders" + ], + "description": "People, organization, product, and channel sidebar rows." + } + }, + "required": [ + "media", + "topics", + "entities" + ], + "description": "Rows served, visible, and blurred per section." + }, + "freshness": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "Delivery class code: 0 live, 1 to 3 delayed classes." + }, + "delayDays": { + "type": "integer", + "description": "Days of newest media withheld. 0 when live." + } + }, + "required": [ + "code", + "delayDays" + ], + "description": "The freshness gate applied." + }, + "chart": { + "type": "object", + "properties": { + "months": { + "type": "integer", + "description": "Months of timeline the plan serves." + } + }, + "required": [ + "months" + ], + "description": "The timeline the plan serves." + }, + "pagination": { + "type": "boolean", + "description": "True when the plan may page past the first page." + }, + "showCounts": { + "type": "boolean", + "description": "True when the plan serves counts. When false, row counts are null." + }, + "withheld": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "media_rows", + "fresh_media", + "sidebar_rows", + "counts", + "chart", + "pagination" + ], + "description": "What was withheld. Values: media_rows (media rows past a position), fresh_media (the newest media), sidebar_rows (sidebar rows past a position), counts (row counts), chart (the timeline), pagination (pages past the first)." + }, + "what": { + "type": "string", + "enum": [ + "media_rows", + "fresh_media", + "sidebar_rows", + "counts", + "chart", + "pagination" + ], + "description": "Repeats kind, for readers written against the older key." + }, + "beyondRow": { + "type": "integer", + "description": "media_rows and sidebar_rows only: rows past this position are withheld." + }, + "windowDays": { + "type": "integer", + "description": "fresh_media only: days of newest media withheld." + }, + "cutoff": { + "type": [ + "string", + "null" + ], + "description": "fresh_media only: the publish date cutoff. Null when none applies." + }, + "section": { + "type": "string", + "enum": [ + "topics", + "entities" + ], + "description": "sidebar_rows only: which sidebar section." + }, + "param": { + "type": [ + "string", + "null" + ], + "enum": [ + "offset", + "cursor", + null + ], + "description": "pagination only: the paging parameter that was refused. Null when none was sent." + } + }, + "required": [ + "kind", + "what" + ] + }, + "description": "Every resource the plan withheld from this response. Empty when view is full." + }, + "unlock": { + "type": [ + "object", + "null" + ], + "properties": { + "tier": { + "type": "string", + "description": "The tier that removes the gate. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise." + }, + "url": { + "type": "string", + "description": "Absolute upgrade or sign-up URL carrying its ?src= attribution." + }, + "src": { + "type": "string", + "enum": [ + "access-envelope", + "api-boundary", + "mcp-tool" + ], + "description": "The surface the unlock link attributes to." + }, + "limitAction": { + "type": [ + "string", + "null" + ], + "enum": [ + "upgrade_to_pro", + "upgrade_or_enable_ondemand", + "upgrade_or_increase_limit", + "enable_ondemand", + "increase_limit", + "contact_sales", + null + ], + "description": "The limit fix when the caller is over its limit. Null otherwise." + } + }, + "required": [ + "tier", + "url", + "src", + "limitAction" + ], + "description": "How to lift the gate. Null when view is full." + } + }, + "required": [ + "v", + "view", + "cls", + "ladder", + "rows", + "freshness", + "chart", + "pagination", + "showCounts", + "withheld", + "unlock" + ], + "description": "Structured access block: what the plan served and what it withheld." + }, + "_dc": { + "type": "integer", + "description": "Delivery class code the freshness filter ran at. Present only when the plan delays media and the response went through that filter." + } + }, + "required": [ + "tier", + "isAuthenticated", + "rowsUsed", + "rowsRemaining", + "monthlyRows", + "isOverLimit", + "onDemandEnabled", + "spendLimitCents", + "currentSpendCents", + "canContinue", + "showingFullData", + "usageLimitType", + "totals", + "limits", + "costPerRowCents", + "isOverage", + "freeLimit", + "access" + ], + "description": "Usage, plan limits, and the access block for this response: what the caller's plan served and what it withheld." + }, + "PersonAppearanceListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Raw appearance row id (the first row for the video), as a string." + }, + "date": { + "type": "string", + "description": "Publish date as locale display text, or Unknown." + }, + "dateRaw": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Null when unknown." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "channel": { + "type": "string", + "description": "Source channel name, or Unknown Channel." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel. Null when unknown." + }, + "channelHandle": { + "type": [ + "string", + "null" + ], + "description": "YouTube handle of the source channel. Null when unknown." + }, + "platform": { + "type": "string", + "enum": [ + "youtube" + ], + "description": "Always youtube." + }, + "type": { + "type": "string", + "enum": [ + "host", + "guest", + "mention" + ], + "description": "host when the person hosts the channel, guest otherwise. mention only when the request passed is_appearance=false, which lists media that mention the person instead." + }, + "context": { + "type": "string", + "description": "Longest description of the appearance, or \"No description available\"." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "thumbnail": { + "type": "string", + "description": "Video thumbnail URL." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "timestamp": { + "type": "string", + "description": "Earliest start timestamp as MM:SS text, or \"Full Episode\" when none." + }, + "rawTimestamp": { + "type": [ + "string", + "null" + ], + "description": "Earliest start timestamp as stored. Null when none." + }, + "duration": { + "type": [ + "number", + "null" + ], + "description": "Video length in seconds. Null when unknown." + }, + "viewCount": { + "type": [ + "integer", + "null" + ], + "description": "Video view count at index time. Null when never fetched." + }, + "excerpt": { + "$ref": "#/components/schemas/PublishedExcerpt" + } + }, + "required": [ + "id", + "date", + "dateRaw", + "title", + "channel", + "channelId", + "channelHandle", + "platform", + "type", + "context", + "sentiment", + "thumbnail", + "videoId", + "timestamp", + "rawTimestamp", + "duration", + "viewCount" + ] + }, + "description": "Media the person appeared in, newest first by default, one row per video." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "_meta" + ] + }, + "EntityTopicListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the topic." + }, + "name": { + "type": "string", + "description": "Topic name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + } + }, + "required": [ + "id", + "name", + "count", + "sentiment" + ] + }, + "description": "Topics that co-occur with the entity in indexed media, highest count first by default." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "exportCapabilities": { + "type": "object", + "properties": { + "highestCount": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the highest-count ordering is always available." + }, + "lowestCount": { + "type": "boolean", + "description": "True when the lowest-count ordering is available." + }, + "alternateSort": { + "type": "boolean", + "description": "True when sort keys other than count are available." + }, + "search": { + "type": "boolean", + "description": "True when the q filter is available." + }, + "availableRows": { + "type": "integer", + "description": "Present only when a stored top-N is smaller than the full result set: the rows it holds." + } + }, + "required": [ + "highestCount", + "lowestCount", + "alternateSort", + "search" + ], + "description": "Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "exportCapabilities", + "_meta" + ] + }, + "EntityPeopleListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the person." + }, + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "id", + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "People that co-occur with the entity in indexed media, highest count first by default." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "peopleMode": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Which co-occurrence the rows count. appearances: guest episodes. mentions: any shared media. Defaults to mentions for organizations and products, appearances otherwise." + }, + "exportCapabilities": { + "type": "object", + "properties": { + "highestCount": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the highest-count ordering is always available." + }, + "lowestCount": { + "type": "boolean", + "description": "True when the lowest-count ordering is available." + }, + "alternateSort": { + "type": "boolean", + "description": "True when sort keys other than count are available." + }, + "search": { + "type": "boolean", + "description": "True when the q filter is available." + }, + "availableRows": { + "type": "integer", + "description": "Present only when a stored top-N is smaller than the full result set: the rows it holds." + } + }, + "required": [ + "highestCount", + "lowestCount", + "alternateSort", + "search" + ], + "description": "Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "peopleMode", + "exportCapabilities", + "_meta" + ] + }, + "EntityOrganizationListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the organization." + }, + "name": { + "type": "string", + "description": "Organization name." + }, + "type": { + "type": "string", + "description": "Stored organization type: organization, company, or brand. Rows served from the stored top-N read organization." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Organization logo URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "id", + "name", + "type", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Organizations that co-occur with the entity in indexed media, highest count first by default." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "exportCapabilities": { + "type": "object", + "properties": { + "highestCount": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the highest-count ordering is always available." + }, + "lowestCount": { + "type": "boolean", + "description": "True when the lowest-count ordering is available." + }, + "alternateSort": { + "type": "boolean", + "description": "True when sort keys other than count are available." + }, + "search": { + "type": "boolean", + "description": "True when the q filter is available." + }, + "availableRows": { + "type": "integer", + "description": "Present only when a stored top-N is smaller than the full result set: the rows it holds." + } + }, + "required": [ + "highestCount", + "lowestCount", + "alternateSort", + "search" + ], + "description": "Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "exportCapabilities", + "_meta" + ] + }, + "EntityProductListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the product." + }, + "name": { + "type": "string", + "description": "Product name." + }, + "type": { + "type": "string", + "description": "Stored entity type. Always product." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "avgSentiment": { + "type": [ + "number", + "null" + ], + "description": "Raw average sentiment score between -1 and 1. Null when no score was computed." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Product logo URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "id", + "name", + "type", + "count", + "avgSentiment", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Products related to the entity, highest count first by default. For an organization these are the products it owns that share media with it; for other types, products that co-occur in indexed media." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "exportCapabilities": { + "type": "object", + "properties": { + "highestCount": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the highest-count ordering is always available." + }, + "lowestCount": { + "type": "boolean", + "description": "True when the lowest-count ordering is available." + }, + "alternateSort": { + "type": "boolean", + "description": "True when sort keys other than count are available." + }, + "search": { + "type": "boolean", + "description": "True when the q filter is available." + }, + "availableRows": { + "type": "integer", + "description": "Present only when a stored top-N is smaller than the full result set: the rows it holds." + } + }, + "required": [ + "highestCount", + "lowestCount", + "alternateSort", + "search" + ], + "description": "Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "exportCapabilities", + "_meta" + ] + }, + "EntityChannelListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the channel." + }, + "name": { + "type": "string", + "description": "Channel name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Distinct media on this channel counted for the entity: appearances for a person, mentions otherwise. Null when the plan hides counts." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Channel avatar URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "id", + "name", + "count", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Channels whose media carry the entity, highest count first by default." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "exportCapabilities": { + "type": "object", + "properties": { + "highestCount": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the highest-count ordering is always available." + }, + "lowestCount": { + "type": "boolean", + "description": "True when the lowest-count ordering is available." + }, + "alternateSort": { + "type": "boolean", + "description": "True when sort keys other than count are available." + }, + "search": { + "type": "boolean", + "description": "True when the q filter is available." + }, + "availableRows": { + "type": "integer", + "description": "Present only when a stored top-N is smaller than the full result set: the rows it holds." + } + }, + "required": [ + "highestCount", + "lowestCount", + "alternateSort", + "search" + ], + "description": "Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "exportCapabilities", + "_meta" + ] + }, + "TopicPageResponse": { + "type": "object", + "properties": { + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Topic name." + }, + "type": { + "type": "string", + "enum": [ + "topic" + ], + "description": "Always topic." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Topic image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "isPriority": { + "type": "boolean", + "description": "True when the entity is flagged priority." + } + }, + "required": [ + "id", + "name", + "type", + "imageUrl", + "imageCheckedAt", + "isPriority" + ], + "description": "The topic." + }, + "stats": { + "type": "object", + "properties": { + "velocity": { + "type": [ + "integer", + "null" + ], + "description": "Mentions in the last 7 days. Null when the plan hides it." + }, + "velocity90d": { + "type": [ + "integer", + "null" + ], + "description": "Mentions in the last 90 days. Null when the plan hides it." + }, + "sentiment": { + "type": "number", + "description": "Reserved. Always 0." + }, + "reach": { + "type": "string", + "description": "Total views as display text, e.g. 1.2M." + }, + "reachRaw": { + "type": "number", + "description": "Total views of the media that mention the topic." + }, + "total": { + "type": "integer", + "description": "Total media that mention the topic." + }, + "latestMediaAt": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date among the counted media. Null when none. The freshness gate does not withhold it." + }, + "people": { + "type": "integer", + "description": "Total people who appeared in media with the topic." + }, + "organizations": { + "type": "integer", + "description": "Total co-occurring organizations." + }, + "products": { + "type": "integer", + "description": "Total co-occurring products." + }, + "topics": { + "type": "integer", + "description": "Total related topics." + }, + "channels": { + "type": "integer", + "description": "Total channels that mention the topic." + } + }, + "required": [ + "velocity", + "velocity90d", + "sentiment", + "reach", + "reachRaw", + "total", + "latestMediaAt", + "people", + "organizations", + "products", + "topics", + "channels" + ], + "description": "Headline numbers and section totals for the topic." + }, + "mentionsByMonth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "Three-letter month name, e.g. Jan." + }, + "yearMonth": { + "type": "string", + "description": "The month as YYYY-MM." + }, + "count": { + "type": "integer", + "description": "Media that month. Months inside the withheld window read 0." + } + }, + "required": [ + "month", + "yearMonth", + "count" + ] + }, + "description": "Media mentioning the topic per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart." + }, + "relatedTopics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Topic name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + } + }, + "required": [ + "name", + "count", + "sentiment" + ] + }, + "description": "Co-occurring topics, highest count first." + }, + "voices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "role": { + "type": "string", + "enum": [ + "Commentator" + ], + "description": "Always Commentator." + } + }, + "required": [ + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt", + "role" + ] + }, + "description": "People who appeared in media with the topic, highest count first." + }, + "companies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Co-occurring organizations, highest count first." + }, + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Co-occurring products, highest count first." + }, + "channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Channel name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media on this channel that mention the entity. Null when the plan hides counts." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "The channel's page key on arcmira.com: its @handle when known, else its UC id. Null when neither is known." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt", + "slug" + ] + }, + "description": "Channels that mention the topic, highest count first." + }, + "mentions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityPageMention" + }, + "description": "Newest media that mention the entity, one row per video, cut to the plan's media rows." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "entity", + "stats", + "mentionsByMonth", + "relatedTopics", + "voices", + "companies", + "products", + "channels", + "mentions", + "_meta" + ] + }, + "EntityPageMention": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Raw appearance row id (the first row for the video), as a string." + }, + "date": { + "type": "string", + "description": "Publish date as locale display text, or Unknown." + }, + "publishedAt": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Null when unknown." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "channel": { + "type": "string", + "description": "Source channel name, or Unknown Channel." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel. Null when unknown." + }, + "channelHandle": { + "type": [ + "string", + "null" + ], + "description": "YouTube handle of the source channel. Null when unknown." + }, + "platform": { + "type": "string", + "enum": [ + "youtube" + ], + "description": "Always youtube." + }, + "thumbnail": { + "type": "string", + "description": "Video thumbnail URL." + }, + "thumbnailUrl": { + "type": "string", + "description": "Video thumbnail URL. Same value as thumbnail." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "duration": { + "type": [ + "number", + "null" + ], + "description": "Video length in seconds. Null when unknown." + }, + "type": { + "type": "string", + "enum": [ + "mention" + ], + "description": "Always mention." + }, + "context": { + "type": "string", + "description": "Description of the mention, or \"No description available\"." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). Absent when no mention in the video carries a score." + }, + "timestamp": { + "type": [ + "string", + "null" + ], + "description": "Earliest mention timestamp as MM:SS text. Null when the mention covers the full episode." + }, + "rawTimestamp": { + "type": [ + "string", + "null" + ], + "description": "Earliest mention timestamp as stored. Null when none." + }, + "mentionCount": { + "type": "integer", + "description": "Mentions of the entity in this video. At least 1." + }, + "allTimestamps": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Every non-zero mention timestamp in the video, as MM:SS text. Absent when there are none." + }, + "excerpt": { + "allOf": [ + { + "$ref": "#/components/schemas/PublishedExcerpt" + }, + { + "description": "A speakerless excerpt naming the entity in this media, when one is active." + } + ] + } + }, + "required": [ + "id", + "date", + "publishedAt", + "title", + "channel", + "channelId", + "channelHandle", + "platform", + "thumbnail", + "thumbnailUrl", + "videoId", + "duration", + "type", + "context", + "timestamp", + "rawTimestamp", + "mentionCount" + ] + }, + "OrganizationPageResponse": { + "type": "object", + "properties": { + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Organization name." + }, + "type": { + "type": "string", + "enum": [ + "organization" + ], + "description": "Always organization." + }, + "category": { + "type": "string", + "description": "Stored type: organization, company, or brand." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "isPriority": { + "type": "boolean", + "description": "True when the entity is flagged priority." + }, + "ownedChannels": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Entity name." + }, + "display_name": { + "type": "string", + "description": "Label that tells a homonym apart, e.g. \"AdQuick (channel)\". Equals name when no label is needed." + }, + "type": { + "type": "string", + "description": "Entity type: channel or product." + }, + "route": { + "type": "string", + "description": "Site-relative route of the entity page on arcmira.com." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Entity image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "videoCount": { + "type": "integer", + "description": "Channels only: media rows published by the channel." + }, + "mentionCount": { + "type": "integer", + "description": "Products only: appearance rows of the product." + } + }, + "required": [ + "id", + "name", + "display_name", + "type", + "route", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Up to 10 channels this entity owns, most videos first. Null when it owns none." + }, + "ownedProducts": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Entity name." + }, + "display_name": { + "type": "string", + "description": "Label that tells a homonym apart, e.g. \"AdQuick (channel)\". Equals name when no label is needed." + }, + "type": { + "type": "string", + "description": "Entity type: channel or product." + }, + "route": { + "type": "string", + "description": "Site-relative route of the entity page on arcmira.com." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Entity image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "videoCount": { + "type": "integer", + "description": "Channels only: media rows published by the channel." + }, + "mentionCount": { + "type": "integer", + "description": "Products only: appearance rows of the product." + } + }, + "required": [ + "id", + "name", + "display_name", + "type", + "route", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Up to 10 products this entity owns, most mentions first. Null when it owns none." + } + }, + "required": [ + "id", + "name", + "type", + "category", + "logoUrl", + "logoCheckedAt", + "isPriority", + "ownedChannels", + "ownedProducts" + ], + "description": "The organization and what it owns." + }, + "roleEdge": { + "type": [ + "object", + "null" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "ceo" + ], + "description": "Always ceo." + }, + "label": { + "type": "string", + "enum": [ + "CEO", + "CO-CEOS" + ], + "description": "CO-CEOS when operator-certified co-CEOs lead the organization, CEO otherwise." + }, + "name": { + "type": "string", + "description": "CEO name, or the co-CEO names joined with \"and\"." + }, + "href": { + "type": [ + "string", + "null" + ], + "description": "Site-relative link to the CEO page. Null for co-CEOs (see people) or when none." + }, + "receipt": { + "type": [ + "string", + "null" + ], + "description": "Display line naming the evidence for the primary CEO. Null when there is none to show." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Primary CEO image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked the primary CEO. Null until checked." + }, + "people": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Co-CEO name." + }, + "href": { + "type": [ + "string", + "null" + ], + "description": "Site-relative link to the co-CEO page. Null when none." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Co-CEO image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this person. Null until checked." + }, + "trueAppearances": { + "type": "integer", + "description": "Media the co-CEO appeared in." + }, + "receipt": { + "type": [ + "string", + "null" + ], + "description": "Display line naming the evidence. Null when there is none to show." + } + }, + "required": [ + "name", + "href", + "imageUrl", + "imageCheckedAt", + "trueAppearances", + "receipt" + ] + }, + "description": "Co-CEOs only: one entry per co-CEO. Absent for a single CEO." + }, + "confirmations": { + "type": "integer", + "description": "Most confirmations of the role across the CEO edges." + }, + "firstConfirmedAt": { + "type": [ + "string", + "null" + ], + "description": "When the primary CEO role was first confirmed. Null when unknown." + }, + "lastConfirmedAt": { + "type": [ + "string", + "null" + ], + "description": "When the primary CEO role was last confirmed. Null when unknown." + }, + "verifiedAt": { + "type": "string", + "description": "When the primary CEO role was verified." + }, + "trueAppearances": { + "type": "integer", + "description": "Most media any of the CEOs appeared in." + }, + "recentAppearances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Video title." + }, + "publishedAt": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Null when unknown." + }, + "videoId": { + "type": [ + "string", + "null" + ], + "description": "YouTube video id. Null when unknown." + }, + "channel": { + "type": [ + "string", + "null" + ], + "description": "Source channel name. Null when unknown." + }, + "channelHandle": { + "type": [ + "string", + "null" + ], + "description": "YouTube handle of the source channel. Null when unknown." + }, + "timestamp": { + "type": [ + "string", + "null" + ], + "description": "Appearance start as MM:SS text. Null when unknown." + } + }, + "required": [ + "title", + "publishedAt", + "videoId", + "channel", + "channelHandle", + "timestamp" + ] + }, + "description": "Newest media the CEO appeared in, outside the withheld window. Empty when no CEO has enough appearances to list." + } + }, + "required": [ + "role", + "label", + "name", + "href", + "receipt", + "imageUrl", + "imageCheckedAt", + "confirmations", + "firstConfirmedAt", + "lastConfirmedAt", + "verifiedAt", + "trueAppearances", + "recentAppearances" + ], + "description": "The verified CEO of the organization. Null when none is verified." + }, + "stats": { + "type": "object", + "properties": { + "velocity": { + "type": [ + "integer", + "null" + ], + "description": "Mentions in the last 90 days. Null when the plan hides it." + }, + "sentiment": { + "type": "number", + "description": "Reserved. Always 0." + }, + "reach": { + "type": "string", + "description": "Total views as display text, e.g. 1.2M." + }, + "reachRaw": { + "type": "number", + "description": "Total views of the media that mention the organization." + }, + "total": { + "type": "integer", + "description": "Total media that mention the organization." + }, + "latestMediaAt": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date among the counted media. Null when none. The freshness gate does not withhold it." + }, + "people": { + "type": "integer", + "description": "Total co-occurring people." + }, + "topics": { + "type": "integer", + "description": "Total co-occurring topics." + }, + "products": { + "type": "integer", + "description": "Owned products with mentions." + }, + "channels": { + "type": "integer", + "description": "Total channels that mention the organization." + } + }, + "required": [ + "velocity", + "sentiment", + "reach", + "reachRaw", + "total", + "latestMediaAt", + "people", + "topics", + "products", + "channels" + ], + "description": "Headline numbers and section totals for the organization." + }, + "mentionsByMonth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "Three-letter month name, e.g. Jan." + }, + "yearMonth": { + "type": "string", + "description": "The month as YYYY-MM." + }, + "count": { + "type": "integer", + "description": "Media that month. Months inside the withheld window read 0." + } + }, + "required": [ + "month", + "yearMonth", + "count" + ] + }, + "description": "Media mentioning the organization per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart." + }, + "topics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Topic name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + } + }, + "required": [ + "name", + "count", + "sentiment" + ] + }, + "description": "Co-occurring topics, highest count first." + }, + "people": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Co-occurring people, highest count first." + }, + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "neutral" + ], + "description": "Always neutral." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Products the organization owns that share media with it, most mentions first." + }, + "channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Channel name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media on this channel that mention the entity. Null when the plan hides counts." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "The channel's page key on arcmira.com: its @handle when known, else its UC id. Null when neither is known." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt", + "slug" + ] + }, + "description": "Channels that mention the organization, highest count first." + }, + "mentions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityPageMention" + }, + "description": "Newest media that mention the entity, one row per video, cut to the plan's media rows." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "entity", + "roleEdge", + "stats", + "mentionsByMonth", + "topics", + "people", + "products", + "channels", + "mentions", + "_meta" + ] + }, + "ProductPageResponse": { + "type": "object", + "properties": { + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Product name." + }, + "type": { + "type": "string", + "enum": [ + "product" + ], + "description": "Always product." + }, + "category": { + "type": "string", + "description": "The stored platform, or Software when none is stored." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "isPriority": { + "type": "boolean", + "description": "True when the entity is flagged priority." + }, + "owner": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the owner." + }, + "name": { + "type": "string", + "description": "Owner name." + }, + "type": { + "type": "string", + "description": "Owner entity type: organization (legacy rows may read company or brand) or person." + }, + "route": { + "type": "string", + "description": "Site-relative route of the owner page on arcmira.com." + } + }, + "required": [ + "id", + "name", + "type", + "route" + ], + "description": "The organization or person that owns this entity. Null when no owner is recorded." + }, + "parentOrg": { + "type": [ + "object", + "null" + ], + "properties": { + "name": { + "type": "string", + "description": "Parent organization name." + }, + "slug": { + "type": "string", + "description": "The name lowercased with spaces as hyphens. Not guaranteed to match the organization page slug." + } + }, + "required": [ + "name", + "slug" + ], + "description": "Legacy: the organization recorded as owning the product in entity relations. Null when none. Prefer owner." + } + }, + "required": [ + "id", + "name", + "type", + "category", + "logoUrl", + "logoCheckedAt", + "isPriority", + "owner", + "parentOrg" + ], + "description": "The product and who owns it." + }, + "stats": { + "type": "object", + "properties": { + "velocity": { + "type": [ + "integer", + "null" + ], + "description": "Mentions in the last 90 days. Null when the plan hides it." + }, + "sentiment": { + "type": "number", + "description": "Reserved. Always 0." + }, + "reach": { + "type": "string", + "description": "Total views as display text, e.g. 1.2M." + }, + "reachRaw": { + "type": "number", + "description": "Total views of the media that mention the product." + }, + "total": { + "type": "integer", + "description": "Total media that mention the product." + }, + "latestMediaAt": { + "type": [ + "string", + "null" + ], + "description": "Newest publish date among the counted media. Null when none. The freshness gate does not withhold it." + }, + "people": { + "type": "integer", + "description": "Total co-occurring people." + }, + "topics": { + "type": "integer", + "description": "Total co-occurring topics." + }, + "organizations": { + "type": "integer", + "description": "Total co-occurring organizations." + }, + "channels": { + "type": "integer", + "description": "Total channels that mention the product." + } + }, + "required": [ + "velocity", + "sentiment", + "reach", + "reachRaw", + "total", + "latestMediaAt", + "people", + "topics", + "organizations", + "channels" + ], + "description": "Headline numbers and section totals for the product." + }, + "opportunities": { + "type": "object", + "properties": { + "evangelists": { + "type": "number", + "enum": [ + 0 + ], + "description": "Not computed. Always 0." + }, + "adInventory": { + "type": "number", + "enum": [ + 0 + ], + "description": "Not computed. Always 0." + } + }, + "required": [ + "evangelists", + "adInventory" + ], + "description": "Reserved for an advertiser view. Both counts are disabled and always 0; 0 means not computed, not a real count." + }, + "mentionsByMonth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "Three-letter month name, e.g. Jan." + }, + "yearMonth": { + "type": "string", + "description": "The month as YYYY-MM." + }, + "count": { + "type": "integer", + "description": "Media that month. Months inside the withheld window read 0." + } + }, + "required": [ + "month", + "yearMonth", + "count" + ] + }, + "description": "Media mentioning the product per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart." + }, + "topics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Topic name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + } + }, + "required": [ + "name", + "count", + "sentiment" + ] + }, + "description": "Up to 10 co-occurring topics, highest count first." + }, + "people": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Up to 10 co-occurring people, highest count first." + }, + "organizations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Up to 10 co-occurring organizations, highest count first." + }, + "channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Channel name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media on this channel that mention the entity. Null when the plan hides counts." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "The channel's page key on arcmira.com: its @handle when known, else its UC id. Null when neither is known." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt", + "slug" + ] + }, + "description": "Up to 6 channels that mention the product, highest count first." + }, + "mentions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityPageMention" + }, + "description": "Newest media that mention the entity, one row per video, cut to the plan's media rows." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "entity", + "stats", + "opportunities", + "mentionsByMonth", + "topics", + "people", + "organizations", + "channels", + "mentions", + "_meta" + ] + }, + "ChannelPageResponse": { + "type": "object", + "properties": { + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id." + }, + "name": { + "type": "string", + "description": "Channel name." + }, + "display_name": { + "type": "string", + "description": "Label that tells the channel apart from an owner of the same name, e.g. \"AdQuick (channel)\". Equals name otherwise." + }, + "type": { + "type": "string", + "enum": [ + "channel" + ], + "description": "Always channel." + }, + "category": { + "type": "string", + "description": "The stored platform, or YouTube when none is stored." + }, + "platform": { + "type": "string", + "description": "The stored platform, or youtube when none is stored." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id (UC form). Null when unknown." + }, + "youtubeChannelId": { + "type": [ + "string", + "null" + ], + "description": "Same value as channelId." + }, + "youtubeHandle": { + "type": [ + "string", + "null" + ], + "description": "YouTube @handle. Null when unknown." + }, + "url": { + "type": [ + "string", + "null" + ], + "description": "Channel URL. Null when unknown." + }, + "avatarUrl": { + "type": [ + "string", + "null" + ], + "description": "Channel avatar URL. Null when unknown." + }, + "avatarCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked the stored avatar. Null when the avatar came from channel metadata instead." + }, + "bannerUrl": { + "type": [ + "string", + "null" + ], + "description": "Channel banner URL. Null when unknown." + }, + "owner": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the owner." + }, + "name": { + "type": "string", + "description": "Owner name." + }, + "type": { + "type": "string", + "description": "Owner entity type: organization (legacy rows may read company or brand) or person." + }, + "route": { + "type": "string", + "description": "Site-relative route of the owner page on arcmira.com." + } + }, + "required": [ + "id", + "name", + "type", + "route" + ], + "description": "The organization or person that owns this entity. Null when no owner is recorded." + } + }, + "required": [ + "id", + "name", + "display_name", + "type", + "category", + "platform", + "channelId", + "youtubeChannelId", + "youtubeHandle", + "url", + "avatarUrl", + "avatarCheckedAt", + "bannerUrl", + "owner" + ], + "description": "The channel and who owns it." + }, + "hosts": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Host names: configured hosts when the channel has them, else discovered ones." + }, + "stats": { + "type": "object", + "properties": { + "velocity": { + "type": "integer", + "description": "Videos published in the last 90 days." + }, + "sentiment": { + "type": "number", + "description": "Reserved. Always 0.5." + }, + "reach": { + "type": "string", + "description": "Average views per video as display text, e.g. 48.2K." + }, + "total": { + "type": "integer", + "description": "Indexed videos of the channel." + } + }, + "required": [ + "velocity", + "sentiment", + "reach", + "total" + ], + "description": "Header vitals. Open on every plan." + }, + "channelInfo": { + "type": "object", + "properties": { + "subscriberCount": { + "type": [ + "string", + "null" + ], + "description": "Subscriber count as YouTube displays it. Null when unknown." + }, + "avgViews": { + "type": [ + "string", + "null" + ], + "description": "Average views per video as display text. Null when the plan hides it." + }, + "episodeCount": { + "type": [ + "integer", + "null" + ], + "description": "Indexed videos. Null when the plan hides it." + }, + "guestCount": { + "type": [ + "integer", + "null" + ], + "description": "Unique guests. Null when the plan hides it." + }, + "startDate": { + "type": [ + "string", + "null" + ], + "description": "Publish date of the oldest indexed video. Null when none." + } + }, + "required": [ + "subscriberCount", + "avgViews", + "episodeCount", + "guestCount", + "startDate" + ], + "description": "Channel details, some gated by plan." + }, + "episodesByMonth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "Three-letter month name, e.g. Jan." + }, + "yearMonth": { + "type": "string", + "description": "The month as YYYY-MM." + }, + "count": { + "type": "integer", + "description": "Media that month. Months inside the withheld window read 0." + } + }, + "required": [ + "month", + "yearMonth", + "count" + ] + }, + "description": "Videos published per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart." + }, + "topics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Topic name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Videos on the channel with the topic. Null when the plan hides counts." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + } + }, + "required": [ + "name", + "count", + "sentiment" + ] + }, + "description": "Topics across the channel, highest count first." + }, + "hosts_detailed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Host name." + }, + "role": { + "type": "string", + "description": "The recorded role cue, or Host." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Appearances by the host. Null when the plan hides counts." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive" + ], + "description": "Always positive." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Host image URL. Null until resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this person. Null until checked." + } + }, + "required": [ + "name", + "role", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "Hosts with their details, in the same order as hosts." + }, + "guests": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Videos on the channel the guest appeared in. Null when the plan hides counts." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + }, + "role": { + "type": "string", + "enum": [ + "Guest" + ], + "description": "Always Guest." + } + }, + "required": [ + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt", + "role" + ] + }, + "description": "Guests who are not hosts, most appearances first." + }, + "organizations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Organizations mentioned across the channel, most mentions first." + }, + "products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Entity name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "logoUrl": { + "type": [ + "string", + "null" + ], + "description": "Logo URL. Null until an image has been resolved." + }, + "logoCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "name", + "count", + "sentiment", + "logoUrl", + "logoCheckedAt" + ] + }, + "description": "Products mentioned across the channel, most mentions first." + }, + "episodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Raw media row id, as a string." + }, + "date": { + "type": "string", + "description": "Publish date as locale display text, or Unknown." + }, + "publishedAt": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Null when unknown." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title." + }, + "channel": { + "type": "string", + "description": "The channel name." + }, + "channelId": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the video, else the channel's. Null when unknown." + }, + "channelHandle": { + "type": [ + "string", + "null" + ], + "description": "The channel's YouTube handle. Null when unknown." + }, + "platform": { + "type": "string", + "enum": [ + "youtube" + ], + "description": "Always youtube." + }, + "thumbnail": { + "type": "string", + "description": "Video thumbnail URL." + }, + "thumbnailUrl": { + "type": "string", + "description": "Video thumbnail URL. Same value as thumbnail." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "duration": { + "type": [ + "number", + "null" + ], + "description": "Video length in seconds. Null when unknown." + }, + "type": { + "type": "string", + "enum": [ + "interview", + "solo" + ], + "description": "interview when a guest appeared, solo otherwise." + }, + "context": { + "type": "string", + "description": "\"Interview with {guest}\" or \"Episode\"." + }, + "sentiment": { + "type": "string", + "enum": [ + "neutral" + ], + "description": "Always neutral." + }, + "timestamp": { + "type": "string", + "enum": [ + "00:00" + ], + "description": "Always 00:00." + }, + "guest": { + "type": [ + "string", + "null" + ], + "description": "One guest who appeared in the video. Null when none." + } + }, + "required": [ + "id", + "date", + "publishedAt", + "title", + "channel", + "channelId", + "channelHandle", + "platform", + "thumbnail", + "thumbnailUrl", + "videoId", + "duration", + "type", + "context", + "sentiment", + "timestamp", + "guest" + ] + }, + "description": "The channel's newest 50 indexed videos. Open on every plan." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + }, + "recommendations_summary": { + "type": "object", + "properties": { + "sponsor_count": { + "type": "integer", + "description": "Recurring sponsors of the channel. 0 when none." + }, + "top_sponsors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the top three sponsors. Present only on a Pro+ plan when the channel has sponsors." + } + }, + "required": [ + "sponsor_count" + ], + "description": "Sponsor teaser for the channel." + } + }, + "required": [ + "entity", + "hosts", + "stats", + "channelInfo", + "episodesByMonth", + "topics", + "hosts_detailed", + "guests", + "organizations", + "products", + "episodes", + "_meta", + "recommendations_summary" + ] + }, + "ChannelGuestListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the person." + }, + "name": { + "type": "string", + "description": "Person name." + }, + "count": { + "type": [ + "integer", + "null" + ], + "description": "Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false)." + }, + "sentiment": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ], + "description": "Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score)." + }, + "imageUrl": { + "type": [ + "string", + "null" + ], + "description": "Person image URL. Null until an image has been resolved." + }, + "imageCheckedAt": { + "type": [ + "string", + "null" + ], + "description": "When the image pipeline last checked this entity. Null until checked." + } + }, + "required": [ + "id", + "name", + "count", + "sentiment", + "imageUrl", + "imageCheckedAt" + ] + }, + "description": "People who appeared on the channel, highest count first by default. count is the episodes they appeared in." + }, + "total": { + "type": "integer", + "description": "Rows matching the filter across all pages." + }, + "offset": { + "type": "integer", + "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." + }, + "limit": { + "type": "integer", + "description": "Page size applied, after the plan clamp." + }, + "hasMore": { + "type": "boolean", + "description": "Same value as has_more, kept for readers of the web shape." + }, + "has_more": { + "type": "boolean", + "description": "True when more rows exist past this page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + }, + "exportCapabilities": { + "type": "object", + "properties": { + "highestCount": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the highest-count ordering is always available." + }, + "lowestCount": { + "type": "boolean", + "description": "True when the lowest-count ordering is available." + }, + "alternateSort": { + "type": "boolean", + "description": "True when sort keys other than count are available." + }, + "search": { + "type": "boolean", + "description": "True when the q filter is available." + }, + "availableRows": { + "type": "integer", + "description": "Present only when a stored top-N is smaller than the full result set: the rows it holds." + } + }, + "required": [ + "highestCount", + "lowestCount", + "alternateSort", + "search" + ], + "description": "Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts." + }, + "_meta": { + "$ref": "#/components/schemas/ExposureMeta" + } + }, + "required": [ + "items", + "total", + "offset", + "limit", + "hasMore", + "has_more", + "next_cursor", + "exportCapabilities", + "_meta" + ] + }, + "MonitorListResponse": { + "type": "object", + "properties": { + "monitors": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/Monitor" + }, + { + "type": "object", + "properties": { + "trackerCount": { + "type": "integer", + "description": "Number of trackers in the monitor." + }, + "alertsThisMonth": { + "type": "integer", + "description": "Alert deliveries written for this monitor since the start of the calendar month." + }, + "slackIntegration": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string", + "description": "Slack integration id." + }, + "team_name": { + "type": [ + "string", + "null" + ], + "description": "Slack workspace name." + }, + "channel_name": { + "type": [ + "string", + "null" + ], + "description": "Slack channel the integration posts to." + } + }, + "required": [ + "id", + "team_name", + "channel_name" + ], + "description": "Display metadata for the connected Slack integration. Null/absent when Slack is not configured." + } + }, + "required": [ + "trackerCount", + "alertsThisMonth" + ] + } + ] + }, + "description": "All monitors for the account, ordered by dashboard sort position, then name." + } + }, + "required": [ + "monitors" + ] + }, + "Monitor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Monitor id." + }, + "name": { + "type": "string", + "description": "Monitor name." + }, + "isCollapsed": { + "type": "boolean", + "description": "True when the monitor is collapsed in the dashboard UI." + }, + "isPaused": { + "type": "boolean", + "description": "True when delivery is paused for all trackers in this monitor. New alerts are not queued, and queued email delivery checks the pause state again before sending. Use PATCH /v1/trackers/{id} with paused: true to pause one tracker." + }, + "sortOrder": { + "type": "integer", + "description": "Dashboard sort position." + }, + "notifyEmails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Configured email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor." + }, + "emailRecipients": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "pending", + "unsubscribed", + "suppressed", + "removed", + "owner_unverified", + "plan_limited" + ] + }, + "invitationStatus": { + "type": "string", + "enum": [ + "sent", + "failed", + "limited", + "pending" + ] + } + }, + "required": [ + "email", + "status" + ] + }, + "description": "Recipient consent and invitation state. An account is not required to accept." + }, + "notifyFrequency": { + "type": "string", + "default": "realtime", + "description": "Delivery cadence. Values: realtime (deliver immediately), hourly (hourly digest), daily (daily digest). Free tier is limited to daily." + }, + "digestDay": { + "type": "string", + "default": "monday", + "description": "Day of week for digest delivery." + }, + "digestTime": { + "type": "string", + "default": "09:00", + "description": "Time of day (HH:MM) for digest delivery." + }, + "notifyWebhook": { + "type": "boolean", + "description": "True when webhook delivery is enabled." + }, + "webhookUrl": { + "type": [ + "string", + "null" + ], + "description": "Webhook destination URL. Null when no webhook is configured." + }, + "webhookSecretSet": { + "type": "boolean", + "description": "True when a webhook signing secret exists for this monitor. The secret itself is never returned on reads; enablement and rotation responses support recovery with the original Idempotency-Key during the valid recovery window." + }, + "webhookSecretHint": { + "type": [ + "string", + "null" + ], + "description": "Last 4 characters of the current signing secret, for identifying which secret you hold. Null until a secret exists." + }, + "webhookFailures": { + "type": "integer", + "default": 0, + "description": "Consecutive webhook delivery failures recorded for this monitor. Reset by a secret rotation or PATCHing notifyWebhook: true; 10 consecutive failures auto-disable a webhook. Note: the delivery pipeline currently accrues failures on the tracker that fired, so this monitor-level counter can lag." + }, + "webhookDisabledAt": { + "type": [ + "string", + "null" + ], + "description": "When the webhook was auto-disabled after repeated failures. Null while delivery is enabled. Re-enable by PATCHing notifyWebhook: true; rotation alone never re-enables." + }, + "webhookDisabledReason": { + "type": [ + "string", + "null" + ], + "description": "Why the webhook was auto-disabled. Null while delivery is enabled." + }, + "notifySlack": { + "type": "boolean", + "description": "True when Slack delivery is enabled." + }, + "slackIntegrationId": { + "type": [ + "string", + "null" + ], + "description": "Slack integration used for delivery. Null when Slack is not configured." + }, + "slackChannelId": { + "type": [ + "string", + "null" + ], + "description": "Slack channel to deliver to. Null when Slack is not configured." + }, + "createdAt": { + "type": "string", + "description": "When the monitor was created." + }, + "updatedAt": { + "type": "string", + "description": "When the monitor was last updated." + } + }, + "required": [ + "id", + "name", + "isCollapsed", + "isPaused", + "sortOrder", + "notifyEmails", + "notifyWebhook", + "webhookUrl", + "webhookSecretSet", + "webhookSecretHint", + "webhookDisabledAt", + "webhookDisabledReason", + "notifySlack", + "slackIntegrationId", + "slackChannelId", + "createdAt", + "updatedAt" + ] + }, + "MonitorMutationResponse": { + "type": "object", + "properties": { + "monitor": { + "allOf": [ + { + "$ref": "#/components/schemas/Monitor" + }, + { + "type": "object", + "properties": { + "trackerCount": { + "type": "integer", + "description": "Number of trackers in the monitor. Always 0 in the create response." + }, + "webhookSecret": { + "type": "string", + "description": "The webhook signing secret (\"whsec_...\"). Only present when this request NEWLY enabled webhook signing: a create with notifyWebhook: true and a webhookUrl, or a PATCH that turns the webhook on (or sets a URL) where no secret existed before. Store it securely. The same Idempotency-Key can recover it for up to 24 hours while it remains the current or valid previous secret. A displaced or expired secret returns idempotency_result_expired without rotating again." + } + }, + "required": [ + "trackerCount" + ] + } + ] + }, + "message": { + "type": "string", + "description": "Human-readable confirmation." + } + }, + "required": [ + "monitor", + "message" + ] + }, + "WebhookSecretRotateResponse": { + "type": "object", + "properties": { + "webhookSecret": { + "type": "string", + "description": "The NEW webhook signing secret (\"whsec_...\"). Store it securely. The same Idempotency-Key can recover it for up to 24 hours while it remains the current or valid previous secret. A displaced or expired secret returns idempotency_result_expired without rotating again." + }, + "webhookSecretHint": { + "type": "string", + "description": "Last 4 characters of the new secret, for identifying which secret you hold." + }, + "previousSecretExpiresAt": { + "type": [ + "string", + "null" + ], + "description": "End of the 24-hour overlap window. Until then, deliveries carry an additional X-Arcmira-Signature-Previous header computed with the previous secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. Null when the monitor had no previous secret (nothing to overlap)." + } + }, + "required": [ + "webhookSecret", + "webhookSecretHint", + "previousSecretExpiresAt" + ] + }, + "MonitorDeleteResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Human-readable confirmation." + }, + "trackersDeleted": { + "type": "integer", + "description": "Number of trackers that were deleted along with the monitor." + } + }, + "required": [ + "message", + "trackersDeleted" + ] + }, + "MonitorTrackersResponse": { + "type": "object", + "properties": { + "trackers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Tracker id." + }, + "entityName": { + "type": "string", + "description": "Tracked entity name." + }, + "entityType": { + "type": "string", + "description": "Tracked entity type." + }, + "displayName": { + "type": "string", + "description": "User-facing display name. Falls back to entityName when not customized." + }, + "isPaused": { + "type": "boolean", + "description": "True when the tracker is paused." + }, + "pausedAt": { + "type": [ + "string", + "null" + ], + "description": "When the tracker was paused. Null unless paused." + }, + "lastNotifiedAt": { + "type": [ + "string", + "null" + ], + "description": "When the tracker last produced an alert. Null until the first alert." + }, + "createdAt": { + "type": "string", + "description": "When the tracker was created." + }, + "updatedAt": { + "type": [ + "string", + "null" + ], + "description": "When the tracker was last updated." + }, + "monitorId": { + "type": "string", + "description": "The monitor id from the request path." + } + }, + "required": [ + "id", + "entityName", + "entityType", + "displayName", + "isPaused", + "pausedAt", + "lastNotifiedAt", + "createdAt", + "updatedAt", + "monitorId" + ] + }, + "description": "Trackers in the monitor, newest first." + }, + "count": { + "type": "integer", + "description": "Number of trackers returned." + } + }, + "required": [ + "trackers", + "count" + ] + }, + "AlertListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Alert" + }, + "description": "Newest alerts first." + }, + "has_more": { + "type": "boolean", + "description": "CURRENTLY always false: this endpoint returns the newest n alerts as a single page and does not paginate." + }, + "next_cursor": { + "type": "null", + "description": "Always null: this endpoint does not paginate." + } + }, + "required": [ + "data", + "has_more", + "next_cursor" + ] + }, + "Alert": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Alert delivery id." + }, + "tracker_id": { + "type": [ + "string", + "null" + ], + "description": "Id of the tracker (tracked entity) that produced the alert." + }, + "monitor_id": { + "type": [ + "string", + "null" + ], + "description": "Id of the monitor the tracker belongs to. Null for trackers outside a monitor." + }, + "entity_id": { + "type": [ + "string", + "null" + ], + "description": "Public id (\"ent_{n}\") of the entity that triggered the alert, when recorded. Null on older rows that were written before entity_id was stored on alert_delivery. Resolve the entity through mention_id or the embedded tracker when this is null." + }, + "mention_id": { + "type": [ + "string", + "null" + ], + "description": "Public id (\"men_{n}\") of the mention/appearance row that triggered the alert. Joins directly against mention rows (e.g. /v1/mentions). Null when not appearance-scoped." + }, + "media_id": { + "type": [ + "integer", + "null" + ], + "description": "Media row that triggered the alert. A raw integer database id, matching the numeric media ids used elsewhere in the API (e.g. mention media.id). Null when not media-scoped." + }, + "appearance_id": { + "type": [ + "integer", + "null" + ], + "description": "Appearance row that triggered the alert. A raw integer database id, matching the numeric appearance_id on mention rows (same number as in mention_id). Null when not appearance-scoped." + }, + "excerpt_id": { + "type": [ + "string", + "null" + ], + "description": "Id of the active mention excerpt used as mention evidence. Null when the alert was sent before evidence was recorded." + }, + "evidence_kind": { + "type": [ + "string", + "null" + ], + "enum": [ + "excerpt", + null + ], + "description": "Which evidence layer was sent. Null on older rows." + }, + "channel": { + "type": "string", + "description": "Delivery channel. Values: email (sent by email), webhook (POSTed to the configured webhook URL), slack (sent to Slack)." + }, + "status": { + "type": "string", + "description": "Delivery status. Values: pending (queued for delivery), sent (delivered), failed (delivery failed; see error_message), skipped (delivery intentionally skipped)." + }, + "final_status": { + "type": [ + "string", + "null" + ], + "description": "Terminal status after retries. Null while delivery is still in progress." + }, + "error_message": { + "type": [ + "string", + "null" + ], + "description": "Error details for failed deliveries. Null unless delivery failed." + }, + "scheduled_at": { + "type": [ + "string", + "null" + ], + "description": "When delivery was scheduled. Null when delivered immediately." + }, + "sent_at": { + "type": [ + "string", + "null" + ], + "description": "When the alert was actually sent. Null until delivery succeeds." + }, + "created_at": { + "type": "string", + "description": "When the alert row was created." + }, + "tracker": { + "type": "object", + "properties": { + "id": { + "type": [ + "string", + "null" + ], + "description": "Tracker id." + }, + "entity_name": { + "type": [ + "string", + "null" + ], + "description": "Tracked entity name." + }, + "entity_type": { + "type": [ + "string", + "null" + ], + "description": "Tracked entity type." + }, + "display_name": { + "type": [ + "string", + "null" + ], + "description": "User-facing display name for the tracker. Null when not customized." + } + }, + "required": [ + "id", + "entity_name", + "entity_type", + "display_name" + ], + "description": "The tracker the alert belongs to. Fields are null when the tracker row was deleted." + }, + "monitor": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string", + "description": "Monitor id." + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Monitor name." + } + }, + "required": [ + "id", + "name" + ], + "description": "The monitor the tracker belongs to. Null for trackers outside a monitor." + } + }, + "required": [ + "id", + "tracker_id", + "monitor_id", + "entity_id", + "mention_id", + "media_id", + "appearance_id", + "excerpt_id", + "evidence_kind", + "channel", + "status", + "final_status", + "error_message", + "scheduled_at", + "sent_at", + "created_at", + "tracker", + "monitor" + ] + }, + "MonitorAddTrackersResponse": { + "type": "object", + "properties": { + "attachedCount": { + "type": "integer", + "description": "Number of unique requested trackers attached." + }, + "message": { + "type": "string", + "description": "Human-readable confirmation, e.g. \"Added 3 tracker(s) to monitor\"." + }, + "monitorId": { + "type": "string", + "description": "The monitor id from the request path." + } + }, + "required": [ + "attachedCount", + "message", + "monitorId" + ] + }, + "TrackerListResponse": { + "type": "object", + "properties": { + "trackers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tracker" + }, + "description": "All trackers for the account, newest first." + }, + "count": { + "type": "integer", + "description": "Number of trackers returned." + } + }, + "required": [ + "trackers", + "count" + ] + }, + "Tracker": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Tracker id in the form \"trk_{hex}\"." + }, + "entityName": { + "type": "string", + "description": "The tracked entity name, as submitted." + }, + "entityType": { + "type": "string", + "description": "The tracked entity type. Values: person, organization, product, topic, channel." + }, + "displayName": { + "type": "string", + "description": "User-facing display name. Falls back to entityName when not customized." + }, + "notifyEmail": { + "type": "boolean", + "description": "True when this tracker delivers by email (default true at creation)." + }, + "notifyWebhook": { + "type": "boolean", + "description": "True when this tracker has a per-tracker webhook override enabled." + }, + "notifySlack": { + "type": "boolean", + "description": "True when this tracker has a per-tracker Slack override enabled." + }, + "webhookUrl": { + "type": [ + "string", + "null" + ], + "description": "Per-tracker webhook destination override. Null when the tracker uses its monitor's delivery settings." + }, + "slackChannelId": { + "type": [ + "string", + "null" + ], + "description": "Per-tracker Slack channel override. Null when not set." + }, + "slackIntegrationId": { + "type": [ + "string", + "null" + ], + "description": "Per-tracker Slack integration override. Null when not set." + }, + "filters": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "Optional matching filters as submitted. Null when none were set." + }, + "isPaused": { + "type": "boolean", + "description": "True when the tracker is paused." + }, + "pausedAt": { + "type": [ + "string", + "null" + ], + "description": "When the tracker was paused. Null unless paused." + }, + "lastNotifiedAt": { + "type": [ + "string", + "null" + ], + "description": "When the tracker last produced an alert. Null until the first alert." + }, + "createdAt": { + "type": "string", + "description": "When the tracker was created." + }, + "updatedAt": { + "type": [ + "string", + "null" + ], + "description": "When the tracker was last updated." + }, + "monitorId": { + "type": "string", + "description": "The monitor this tracker belongs to. Absent for standalone trackers." + }, + "emailDeliveryCount": { + "type": "integer", + "description": "Email deliveries in the current billing period." + }, + "webhookDeliveryCount": { + "type": "integer", + "description": "Webhook deliveries in the current billing period." + }, + "slackDeliveryCount": { + "type": "integer", + "description": "Slack deliveries in the current billing period." + } + }, + "required": [ + "id", + "entityName", + "entityType", + "displayName", + "notifyEmail", + "notifyWebhook", + "notifySlack", + "webhookUrl", + "slackChannelId", + "slackIntegrationId", + "filters", + "isPaused", + "pausedAt", + "lastNotifiedAt", + "createdAt", + "updatedAt", + "emailDeliveryCount", + "webhookDeliveryCount", + "slackDeliveryCount" + ] + }, + "TrackerMutationResponse": { + "type": "object", + "properties": { + "tracker": { + "$ref": "#/components/schemas/Tracker" + }, + "message": { + "type": "string", + "description": "Human-readable confirmation." + } + }, + "required": [ + "tracker", + "message" + ] + }, + "MessageResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Human-readable confirmation." + } + }, + "required": [ + "message" + ] + }, + "TeamMembersResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamMember" + }, + "description": "Active members, earliest join first. Removed members are excluded." + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Team id." + }, + "name": { + "type": "string", + "description": "Team name." + } + }, + "required": [ + "id", + "name" + ] + } + }, + "required": [ + "data", + "team" + ] + }, + "TeamMember": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "description": "Id of the member's user account." + }, + "name": { + "type": "string", + "description": "Member display name." + }, + "email": { + "type": "string", + "description": "Member email address." + }, + "role": { + "type": "string", + "enum": [ + "owner", + "admin", + "member", + "unpaid_admin" + ], + "description": "Team role. Values: owner, admin, member, unpaid_admin. Unpaid admins manage the team without a paid seat and have no product access." + }, + "seat_type": { + "type": "string", + "enum": [ + "standard", + "premium", + "free" + ], + "description": "Seat type. Values: standard (5,000 rows/month included), premium (25,000 rows/month included), free (the Unpaid Admin seat: no product access)." + }, + "joined_at": { + "type": [ + "string", + "null" + ], + "description": "When the member joined the team. Null when unknown." + } + }, + "required": [ + "user_id", + "name", + "email", + "role", + "seat_type", + "joined_at" + ] + }, + "TeamSpendResponse": { + "type": "object", + "properties": { + "period_start": { + "type": "string", + "description": "First day of the current period (YYYY-MM-DD)." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamMemberSpend" + }, + "description": "Per-member spend rows, earliest join first." + } + }, + "required": [ + "period_start", + "data" + ] + }, + "TeamMemberSpend": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "description": "Id of the member's user account." + }, + "name": { + "type": "string", + "description": "Member display name." + }, + "email": { + "type": "string", + "description": "Member email address." + }, + "role": { + "type": "string", + "enum": [ + "owner", + "admin", + "member", + "unpaid_admin" + ], + "description": "Team role. Values: owner, admin, member, unpaid_admin. Unpaid admins manage the team without a paid seat and have no product access." + }, + "seat_type": { + "type": "string", + "enum": [ + "standard", + "premium", + "free" + ], + "description": "Seat type. Values: standard (5,000 rows/month included), premium (25,000 rows/month included), free (the Unpaid Admin seat: no product access)." + }, + "rows_used": { + "type": "integer", + "description": "Rows this member consumed in the current period." + }, + "on_demand_spend_cents": { + "type": "integer", + "description": "Account-wide on-demand overage spend for this member in the current period, in US cents." + }, + "on_demand_enabled": { + "type": "boolean", + "description": "The member account's on-demand preference. Effective admission also depends on the team setting and remaining spend limit." + } + }, + "required": [ + "user_id", + "name", + "email", + "role", + "seat_type", + "rows_used", + "on_demand_spend_cents", + "on_demand_enabled" + ] + }, + "TeamUsageEventsResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamUsageEvent" + }, + "description": "Usage events across all team members, newest first." + }, + "has_more": { + "type": "boolean", + "description": "True when another page exists." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Opaque cursor for the next page. Null on the last page." + } + }, + "required": [ + "data", + "has_more", + "next_cursor" + ] + }, + "TeamUsageEvent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Usage event id." + }, + "user_id": { + "type": [ + "string", + "null" + ], + "description": "Id of the member who generated the event." + }, + "action": { + "type": "string", + "description": "Usage action, e.g. entity_view, search, export." + }, + "entity_type": { + "type": [ + "string", + "null" + ], + "description": "Entity type the event touched, when applicable." + }, + "entity_name": { + "type": [ + "string", + "null" + ], + "description": "Entity name the event touched, when applicable." + }, + "total_rows": { + "type": "integer", + "description": "Total rows returned by the request." + }, + "premium_rows": { + "type": "integer", + "description": "Rows charged against the member's allowance (total minus free rows)." + }, + "request_path": { + "type": [ + "string", + "null" + ], + "description": "API path that generated the event." + }, + "is_overage": { + "type": "boolean", + "description": "True when this event billed on-demand rows past the included allowance." + }, + "created_at": { + "type": [ + "string", + "null" + ], + "description": "When the event was recorded." + } + }, + "required": [ + "id", + "user_id", + "action", + "entity_type", + "entity_name", + "total_rows", + "premium_rows", + "request_path", + "is_overage", + "created_at" + ] + }, + "TranscriptResponse": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "ready" + ] + }, + "video": { + "$ref": "#/components/schemas/TranscriptVideo" + }, + "quality": { + "type": "string", + "enum": [ + "captions", + "premium" + ], + "description": "The requested quality. Premium is served only with an owned unlock; it never falls back to captions." + }, + "source": { + "type": "string", + "enum": [ + "creator_captions", + "third_party_quick", + "arcmira_premium" + ], + "description": "Public source class. creator_captions were written or approved by the channel, third_party_quick are YouTube automatic captions, arcmira_premium is our own diarized transcript." + }, + "language": { + "type": "string", + "description": "The resolved track code, asr-en style when the track is automatic." + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaptionTrack" + }, + "description": "Every caption track the video offers. Empty when we did not list them on this call." + }, + "lines": { + "type": "array", + "items": { + "type": "object", + "properties": { + "start": { + "type": "number", + "description": "Line start in seconds from the beginning of the video." + }, + "end": { + "type": "number", + "description": "Line end in seconds." + }, + "text": { + "type": "string" + }, + "speaker": { + "type": "integer", + "description": "The person saying this line, present on every Premium line. Join it against speakers[].id." + }, + "index": { + "type": "integer", + "description": "Line index, present on every Premium line. Echo it as anchor.segmentIndex when you correct the line." + } + }, + "required": [ + "start", + "end", + "text" + ] + }, + "description": "Present when timestamps is true. Cite start with watch_url." + }, + "paragraphs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "start": { + "type": "number", + "description": "Paragraph start in seconds." + }, + "text": { + "type": "string" + }, + "speaker": { + "type": "integer" + } + }, + "required": [ + "start", + "text" + ] + }, + "description": "Present when timestamps is false. Lines joined on speaker changes for Premium and on sentence boundaries for captions." + }, + "speakers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Speaker id, numbered in the order people first speak. Only meaningful with this read and its revision." + }, + "name": { + "type": "string", + "description": "The identified person, or Speaker 1, Speaker 2 and so on for a voice nobody has identified yet." + }, + "entity_id": { + "type": [ + "integer", + "null" + ], + "description": "Raw entity id of the identified person. Null when the speaker is unidentified." + }, + "confidence": { + "type": [ + "string", + "null" + ], + "description": "high when the name was reviewed, low when it is your own identification still awaiting review, null when nobody is identified." + } + }, + "required": [ + "id", + "name", + "entity_id", + "confidence" + ] + }, + "description": "Premium only. Speaker identification is right most of the time and wrong sometimes; say it came from Arcmira when a name matters." + }, + "revision": { + "type": "string", + "description": "Premium reads only. Opaque id of the transcript you were served, the approved corrections on it, and who speaks each line. Echo it on every correction; a 409 means it changed underneath you, so read again." + }, + "range": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": [ + "start", + "end" + ], + "description": "Echoed when you sent start and end. Lines overlapping the window are returned. On captions only the window is billed; Premium retrieval is free." + }, + "rows_billed": { + "type": "integer", + "description": "Rows this call charged. 0 on a repeat of the same video, quality, language, and range inside the 7 day dedupe window, and always 0 on Premium retrieval." + }, + "as_of": { + "type": [ + "string", + "null" + ], + "description": "When the transcript was produced." + }, + "premium_job": { + "type": "object", + "properties": { + "job_id": { + "type": [ + "string", + "null" + ], + "description": "Transcription request id. Poll it with GET /v1/transcriptions/{id}." + }, + "status": { + "type": "string", + "description": "Pipeline status at submit time." + }, + "next_poll_seconds": { + "type": [ + "integer", + "null" + ], + "description": "Seconds to wait before polling again." + }, + "eta_seconds": { + "type": [ + "integer", + "null" + ], + "description": "Estimated seconds until the Premium transcript is ready." + } + }, + "required": [ + "job_id", + "status", + "next_poll_seconds", + "eta_seconds" + ], + "description": "Reserved for job metadata. Pending Premium retrieval uses its separate 202 response." + }, + "access": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error" + ], + "description": "The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling." + }, + "code": { + "type": "string", + "description": "The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first." + }, + "reason": { + "type": "string", + "enum": [ + "no_credential", + "invalid", + "revoked" + ], + "description": "Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable." + }, + "message": { + "type": "string", + "description": "One plain line. Names the fix or the unlock." + }, + "param": { + "type": "string", + "description": "The query or body parameter the gate refused, when one did." + }, + "gate": { + "type": "string", + "enum": [ + "rows", + "key", + "plan", + "freshness", + "exposure_law", + "rate", + "pagination" + ], + "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." + }, + "unlock": { + "type": "object", + "properties": { + "tier": { + "type": "string", + "description": "The plan that lifts the gate." + }, + "url": { + "type": "string", + "description": "Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim." + }, + "offer": { + "type": "null", + "description": "Reserved for the agent-discount offer. Always null today." + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the call does. send_signup_code sends a verification code to an address for an account key." + }, + "method": { + "type": "string", + "description": "HTTP method to use." + }, + "url": { + "type": "string", + "description": "Absolute endpoint carrying its ?src= attribution. Call it verbatim." + } + }, + "required": [ + "kind", + "method", + "url" + ], + "description": "The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens." + } + }, + "required": [ + "tier", + "url", + "offer" + ], + "description": "How to lift the gate. Present when the gate has an unlock." + }, + "retry_after_seconds": { + "type": "integer", + "description": "Present on rate gates. Mirrors the Retry-After header." + }, + "doc_url": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "type", + "code", + "message", + "doc_url", + "request_id" + ], + "description": "The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would." + }, + "note": { + "type": "string", + "description": "One steering sentence for the agent reading this. On Premium it is the diarization disclosure verbatim." + } + }, + "required": [ + "state", + "video", + "quality", + "source", + "language", + "languages", + "rows_billed", + "as_of", + "note" + ], + "examples": [ + { + "video": { + "id": "dQw4w9WgXcQ", + "title": "TBPN | Tuesday, August 4", + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "published_at": "2026-08-04T17:00:00.000Z", + "duration_seconds": 10800, + "watch_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + "quality": "captions", + "source": "creator_captions", + "language": "en", + "languages": [ + { + "code": "en", + "name": "English", + "generated": false + } + ], + "lines": [ + { + "start": 4787, + "end": 4791.5, + "text": "Ramp has been on the show for a while now." + }, + { + "start": 4791.5, + "end": 4796, + "text": "The pitch is still the same, spend less time on expenses." + } + ], + "rows_billed": 12, + "as_of": "2026-08-04T18:12:00.000Z", + "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got." + }, + { + "video": { + "id": "aB3dE5fG7hI", + "title": "Moment of Truth | The permitting fight nobody watched", + "channel_id": "UClWkDGXEzsh77GAhs90wpXw", + "channel_name": "Moment of Truth", + "published_at": "2026-08-19T14:00:00.000Z", + "duration_seconds": 4500, + "watch_url": "https://www.youtube.com/watch?v=aB3dE5fG7hI" + }, + "quality": "premium", + "source": "arcmira_premium", + "language": "en", + "languages": [ + { + "code": "en", + "name": "English", + "generated": false + } + ], + "lines": [ + { + "start": 612, + "end": 617.5, + "text": "The permit sat in review for nineteen months.", + "speaker": 0, + "index": 0 + }, + { + "start": 617.5, + "end": 623, + "text": "And the second review started before the first one closed.", + "speaker": 1, + "index": 1 + } + ], + "speakers": [ + { + "id": 0, + "name": "Dana Whitfield", + "entity_id": 4821, + "confidence": "high" + }, + { + "id": 1, + "name": "Speaker 1", + "entity_id": null, + "confidence": null + } + ], + "revision": "rev_7f2c1a.0k3m9x", + "rows_billed": 375, + "as_of": "2026-08-19T16:40:00.000Z", + "note": "Premium transcripts are Arcmira's own. Every Premium transcript is diarized: each line carries a speaker. We identify each speaker from the audio, the video, and internal and community review. We are right most of the time and wrong sometimes, so when a name matters, say it came from Arcmira's speaker identification and cite the line." + } + ] + }, + "TranscriptVideo": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "title": { + "type": "string", + "description": "Video title. Empty when we could not read it." + }, + "channel_id": { + "type": [ + "string", + "null" + ], + "description": "YouTube channel id of the source channel." + }, + "channel_name": { + "type": [ + "string", + "null" + ] + }, + "published_at": { + "type": [ + "string", + "null" + ], + "description": "Publish timestamp. Cite it as the date of anything you quote." + }, + "duration_seconds": { + "type": [ + "number", + "null" + ], + "description": "Video length in seconds. Null when unknown, which also means the row estimate was unknown." + }, + "watch_url": { + "type": "string", + "description": "Canonical YouTube watch URL." + } + }, + "required": [ + "id", + "title", + "channel_id", + "channel_name", + "published_at", + "duration_seconds", + "watch_url" + ] + }, + "CaptionTrack": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Caption track code, e.g. en or de. Pass it as language to select this track." + }, + "name": { + "type": "string", + "description": "Track name as YouTube reports it." + }, + "generated": { + "type": "boolean", + "description": "True for YouTube automatic captions, false for a track the channel wrote or approved." + } + }, + "required": [ + "code", + "name", + "generated" + ] + }, + "TranscriptPending": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "pending" + ] + }, + "quality": { + "type": "string", + "enum": [ + "premium" + ] + }, + "premium_job": { + "type": "object", + "properties": { + "job_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "next_poll_seconds": { + "type": "number" + }, + "eta_seconds": { + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "job_id", + "status", + "next_poll_seconds", + "eta_seconds" + ] + }, + "status_url": { + "type": "string" + }, + "next_poll_seconds": { + "type": "number" + } + }, + "required": [ + "state", + "quality", + "premium_job", + "status_url", + "next_poll_seconds" + ] + }, + "TranscriptPurchaseQuote": { + "type": "object", + "properties": { + "video_id": { + "type": "string" + }, + "duration_seconds": { + "type": "number" + }, + "billing_scope": { + "type": "string", + "enum": [ + "full_video" + ] + }, + "owned": { + "type": "boolean" + }, + "eligible": { + "type": "boolean" + }, + "quote": { + "$ref": "#/components/schemas/TranscriptQuote" + }, + "charge": { + "type": "object", + "properties": { + "unit": { + "type": "string", + "enum": [ + "rows", + "credits" + ] + }, + "amount": { + "type": "number" + } + }, + "required": [ + "unit", + "amount" + ] + }, + "credits_per_row": { + "type": "number" + }, + "max_on_demand_cents": { + "type": "number" + }, + "on_demand_cents_per_unit": { + "type": "number" + }, + "prepare_url": { + "type": "string" + }, + "refund_policy": { + "type": "string" + } + }, + "required": [ + "video_id", + "duration_seconds", + "billing_scope", + "owned", + "eligible", + "quote", + "charge", + "credits_per_row", + "max_on_demand_cents", + "on_demand_cents_per_unit", + "prepare_url", + "refund_policy" + ] + }, + "TranscriptQuote": { + "type": "object", + "properties": { + "quarters": { + "type": "integer", + "description": "Number of 15-minute blocks in the video, ceiling'd, minimum 1." + }, + "rows": { + "type": "integer", + "description": "Total unlock cost in rows: 75 rows per 15-minute block." + } + }, + "required": [ + "quarters", + "rows" + ] + }, + "VideoCaptionsResponse": { + "type": "object", + "properties": { + "video": { + "$ref": "#/components/schemas/TranscriptVideo" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaptionTrack" + }, + "description": "Every caption track the video offers. Pass a code, or a comma-separated priority list, as language on GET /v1/transcripts/{video_id}." + } + }, + "required": [ + "video", + "languages" + ], + "example": { + "video": { + "id": "kJQP7kiw5Fk", + "title": "Moment of Truth | The founder interview", + "channel_id": "UClWkDGXEzsh77GAhs90wpXw", + "channel_name": "Moment of Truth", + "published_at": "2026-08-21T16:00:00.000Z", + "duration_seconds": 3720, + "watch_url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk" + }, + "languages": [ + { + "code": "en", + "name": "English", + "generated": false + }, + { + "code": "de", + "name": "German", + "generated": true + } + ] + } + }, + "TranscriptionSubmitResponse": { + "type": "object", + "properties": { + "request": { + "$ref": "#/components/schemas/TranscriptionRequest" + }, + "existing": { + "type": "boolean", + "description": "True when an in-flight (or already-satisfied) request for the same video was returned instead of creating a new one." + }, + "overLimit": { + "type": "boolean", + "description": "Only present (true) when this purchase consumed the rest of the included row allocation." + } + }, + "required": [ + "request" + ] + }, + "TranscriptionRequest": { + "type": "object", + "properties": { + "id": { + "type": [ + "string", + "null" + ], + "description": "Transcription request id (UUID). Null only in the degenerate submit response for a video you already own that has no request history." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "status": { + "type": "string", + "enum": [ + "queued", + "downloading", + "transcribing", + "analyzing", + "complete", + "failed", + "refund_pending", + "refunded" + ], + "description": "Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked)." + }, + "state": { + "type": "string", + "enum": [ + "pending", + "ready", + "failed", + "refunded" + ] + }, + "charge": { + "type": "object", + "properties": { + "unit": { + "type": "string", + "enum": [ + "rows", + "credits" + ] + }, + "amount": { + "type": "number" + }, + "credits_per_row": { + "type": "number" + } + }, + "required": [ + "unit", + "amount", + "credits_per_row" + ], + "description": "Accepted charge units. Present on durable purchases; absent only on legacy requests." + }, + "stage": { + "type": [ + "string", + "null" + ], + "enum": [ + "queued", + "transcribing", + "analyzing", + null + ], + "description": "User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses." + }, + "quote": { + "allOf": [ + { + "$ref": "#/components/schemas/TranscriptQuote" + }, + { + "description": "What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free." + } + ] + }, + "etaSeconds": { + "type": "integer", + "description": "Estimated SECONDS until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight." + }, + "nextPollSeconds": { + "type": "integer", + "description": "Recommended SECONDS to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight." + }, + "error": { + "type": "string", + "description": "Failure reason. Only present when status is failed or refunded." + }, + "refunded": { + "type": "boolean", + "description": "True when the charged rows were returned. Only present when status is failed or refunded." + }, + "createdAt": { + "type": "string", + "description": "When the request was submitted." + }, + "completedAt": { + "type": "string", + "description": "When the request reached a terminal status. Absent while in flight." + } + }, + "required": [ + "id", + "videoId", + "status", + "state", + "stage", + "quote", + "createdAt" + ] + }, + "TranscriptionListResponse": { + "type": "object", + "properties": { + "requests": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/TranscriptionRequest" + }, + { + "type": "object", + "properties": { + "title": { + "type": [ + "string", + "null" + ], + "description": "Video title for display. Null when unknown." + } + }, + "required": [ + "title" + ] + } + ] + }, + "description": "Your requests in descending creation time and id order, up to the requested limit." + }, + "has_more": { + "type": "boolean", + "description": "True when another page exists in this traversal." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Signed continuation for the same filter, limit and credential; null on the last page." + } + }, + "required": [ + "requests", + "has_more", + "next_cursor" + ] + }, + "CorrectionAcceptedResponse": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true on acceptance." + }, + "kind": { + "type": "string", + "enum": [ + "line_edit", + "speaker_reassign", + "speaker_identify", + "add_person", + "entity_tag", + "segment_rewrite" + ], + "description": "The correction kind, echoed back." + }, + "result": { + "type": "object", + "additionalProperties": {}, + "description": "The stored pending-review row (kind-specific fields). Always carries the row id: use it with the matching withdrawal DELETE." + } + }, + "required": [ + "ok", + "kind", + "result" + ] + }, + "CorrectionSeqMismatchResponse": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Always \"Out-of-order correction.\"." + }, + "expectedSeq": { + "type": "integer", + "description": "The seq the server expects next for this video. Rebase local counters onto it and resend." + } + }, + "required": [ + "error", + "expectedSeq" + ] + }, + "WithdrawnResponse": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true: the pending row was withdrawn." + } + }, + "required": [ + "ok" + ] + }, + "TranscriptEditSubmittedResponse": { + "type": "object", + "properties": { + "edit": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Pending row id. Withdraw it with DELETE /v1/transcripts/{video_id}/edits/{id}." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "segmentIndex": { + "type": "integer", + "description": "The line index the edit targets." + }, + "correctedText": { + "type": "string", + "description": "The corrected line text as stored, trimmed." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "withdrawn" + ], + "description": "Review status. Always pending on submit." + } + }, + "required": [ + "id", + "videoId", + "segmentIndex", + "correctedText", + "status" + ], + "description": "The stored pending edit. It replaces any pending edit you had on the same line." + } + }, + "required": [ + "edit" + ] + }, + "SpeakerIdentificationSubmittedResponse": { + "type": "object", + "properties": { + "identification": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Pending row id. Withdraw it with DELETE /v1/transcripts/{video_id}/speakers/{id}." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "speakerId": { + "type": "integer", + "description": "The speaker id you sent, from the transcript read's speakers[]." + }, + "entity": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Raw integer entity id of the person." + }, + "name": { + "type": "string", + "description": "Person name." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "Person slug. Null when never slugged." + } + }, + "required": [ + "id", + "name", + "slug" + ], + "description": "The person the speaker now points at: the entity you named, an existing person matching the name, or a newly created one." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "withdrawn", + "reverted" + ], + "description": "Review status. Always pending on submit." + } + }, + "required": [ + "id", + "videoId", + "speakerId", + "entity", + "status" + ], + "description": "The stored pending identification." + } + }, + "required": [ + "identification" + ] + }, + "VideoMergeSubmittedResponse": { + "type": "object", + "properties": { + "merge": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Pending row id. Withdraw it with DELETE /v1/transcripts/{video_id}/merges/{id}." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "sourceName": { + "type": "string", + "description": "The name as it appears in the video." + }, + "targetEntityId": { + "type": "integer", + "description": "Raw integer entity id the name refers to." + }, + "replaceWith": { + "type": [ + "string", + "null" + ], + "description": "Respelling applied to the transcript text. Null when none." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "reverted" + ], + "description": "Review status. Always pending on submit." + } + }, + "required": [ + "id", + "videoId", + "sourceName", + "targetEntityId", + "replaceWith", + "status" + ], + "description": "The stored pending merge." + } + }, + "required": [ + "merge" + ] + }, + "VideoMergeListResponse": { + "type": "object", + "properties": { + "merges": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Pending row id." + }, + "videoId": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, + "sourceName": { + "type": "string", + "description": "The name as it appears in the video." + }, + "targetEntityId": { + "type": "integer", + "description": "Raw integer entity id the name refers to." + }, + "targetName": { + "type": "string", + "description": "Name of the target entity." + }, + "replaceWith": { + "type": [ + "string", + "null" + ], + "description": "Respelling applied to the transcript text. Null when none." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "reverted" + ], + "description": "Review status. Always pending: only pending rows are listed." + }, + "createdAt": { + "type": "string", + "description": "When the merge was submitted." + } + }, + "required": [ + "id", + "videoId", + "sourceName", + "targetEntityId", + "targetName", + "replaceWith", + "status", + "createdAt" + ] + }, + "description": "Your pending merges for the video, newest first." + } + }, + "required": [ + "merges" + ] + } + }, + "parameters": {} + }, + "paths": { + "/v1/health": { + "get": { + "tags": [ + "Meta" + ], + "operationId": "get_health", + "summary": "Health check", + "security": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry-After carries the wait.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/openapi.json": { + "get": { + "tags": [ + "Meta" + ], + "operationId": "get_openapi_document", + "summary": "OpenAPI document", + "security": [], + "responses": { + "200": { + "description": "This OpenAPI 3.1 document.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenApiDocument" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "description": "Rate limit exceeded. Retry-After carries the wait.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/me": { + "get": { + "tags": [ + "Meta" + ], + "operationId": "get_me", + "summary": "Current API key context", + "description": "Available even when the account has exhausted its usage allowance. Returns the credential making the request (key_id, key_label, credential_kind), the masked account email, the tier, scopes, rate limit, row usage with period_resets_at, and account settings. settings.transcripts is what a transcript request that names no parameter of its own receives: every key of the account resolves against it.", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/me/settings": { + "patch": { + "tags": [ + "Meta" + ], + "operationId": "update_my_settings", + "summary": "Set account defaults", + "description": "Sets the account defaults every key of the account resolves against. Send only the fields you are changing; an omitted field keeps the value it has. Resolution order for every transcript request is the explicit parameter, then these settings, then the platform default, so a default never overrides a parameter the caller sent. Defaults live on the account, never on a key: two keys of one account answer the same request the same way. The response echoes the resolved settings.", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transcripts": { + "type": "object", + "properties": { + "quality": { + "type": "string", + "enum": [ + "captions", + "premium" + ], + "description": "Default transcript quality for this account: captions or premium. premium reads require an existing purchase. Owned transcripts remain readable after a plan downgrade; new purchases require an eligible plan." + }, + "language": { + "type": "string", + "description": "Default comma-separated caption language priority list, tried in order (e.g. \"de,en\"), at most 5 codes. Use asr for the first automatic track and asr- for a specific one." + }, + "timestamps": { + "type": "boolean", + "description": "Default for the timestamps parameter. false makes paragraphs[] the default body shape instead of lines[]." + } + }, + "description": "Fields to change. An omitted field keeps the value the account already carries." + } + }, + "required": [ + "transcripts" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Settings updated", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeSettingsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/signups": { + "post": { + "tags": [ + "Meta" + ], + "operationId": "create_signup", + "summary": "Send a verification code to an address", + "description": "Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { \"email\": \"agent@example.com\" }, with an optional \"src\" naming the surface that sent you.", + "security": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "maxLength": 254, + "description": "The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget." + }, + "src": { + "type": "string", + "maxLength": 32, + "description": "The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back." + } + }, + "required": [ + "email" + ] + } + } + } + }, + "responses": { + "202": { + "description": "Code sent", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignupSentResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "description": "Rate limit exceeded. Retry-After carries the wait.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "$ref": "#/components/responses/ServerError" + }, + "503": { + "description": "server_error. The verification email could not be sent. Retry-After carries the wait.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/signups/verify": { + "post": { + "tags": [ + "Meta" + ], + "operationId": "verify_signup", + "summary": "Exchange a verification code for an account key", + "description": "Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { \"email\": \"agent@example.com\", \"code\": \"482913\" }.", + "security": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "maxLength": 254, + "description": "The address the code was sent to." + }, + "code": { + "type": "string", + "pattern": "^\\d+$", + "description": "The six digit code from the email. Ten minutes, five attempts, then a new send is required." + } + }, + "required": [ + "email", + "code" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Account key minted", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignupVerifiedResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "description": "Rate limit exceeded. Retry-After carries the wait.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/search": { + "get": { + "tags": [ + "Search" + ], + "operationId": "search", + "summary": "Resolve a name to a single entity", + "description": "Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "description": "Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. \"Ford\" resolving to Ford Motor Company) are honored." + }, + "required": true, + "description": "Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. \"Ford\" resolving to Ford Motor Company) are honored.", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ], + "description": "Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows." + }, + "required": false, + "description": "Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows.", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResolveResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/search": { + "get": { + "tags": [ + "Entities" + ], + "operationId": "search_entities", + "summary": "Fuzzy entity discovery", + "description": "Substring name search returning up to 25 entities ordered by appearance count, each with a suggested flag: true on an exact match with far more traction than any other row of its type. A q that is a YouTube channel id (UC...), an @handle, or a YouTube URL carrying either resolves to that one channel row, suggested, with its youtube_channel_id. Callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary per result when a brand profile exists.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 2 + }, + "required": true, + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ] + }, + "required": false, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "has_recommendations_data", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntitySearchResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/resolve": { + "get": { + "tags": [ + "Entities" + ], + "operationId": "resolve_entity", + "summary": "A name to one id, before any filter", + "description": "Call this before passing an id to about, by, entity_ids, channel_ids or channel; those filters refuse names with id_required. Pass context with the user's own words about the name (\"the startup bank\", \"on My First Million\"). The answer is one of three: best (the name means one row: use it and name it), suggested (no row is certain but one stands out, with reason and evidence: use it and tell the user you assumed it), or ask (several rows fit: show ask.options, or check every option id and answer per row). For a show pass type=channel and use the youtube_channel_id; for a brand or a person use the id. Free; bills no rows.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 2, + "description": "A name, @handle, YouTube URL or channel id (UC...). One thing per call." + }, + "required": true, + "description": "A name, @handle, YouTube URL or channel id (UC...). One thing per call.", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ], + "description": "Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id." + }, + "required": false, + "description": "Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id.", + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 15, + "default": 8, + "description": "Candidates to return, 1 to 15. Default 8." + }, + "required": false, + "description": "Candidates to return, 1 to 15. Default 8.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "minLength": 2, + "maxLength": 300, + "description": "What the user said about the name, in their words (\"the startup bank\", \"Canada's prime minister\", \"on My First Million\"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context." + }, + "required": false, + "description": "What the user said about the name, in their words (\"the startup bank\", \"Canada's prime minister\", \"on My First Million\"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context.", + "name": "context", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityResolveResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/lookup": { + "get": { + "tags": [ + "Entities" + ], + "operationId": "lookup_entity", + "summary": "Resolve an entity to its canonical stable ID", + "description": "Resolves an id or name to the canonical entity record, following merge redirects. Pass either id (ent_{n} or numeric) or name, optionally constrained by type.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": false, + "name": "id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "name", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ] + }, + "required": false, + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityLookupResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/cards": { + "get": { + "tags": [ + "Entities" + ], + "operationId": "get_entity_cards", + "summary": "Batch compact entity cards by id", + "description": "Free endpoint (0 rows). Batched compact lookups for UI hover cards: name, slug, type, image, and index counts for up to 50 raw integer entity ids per request. Merged ids resolve to their canonical entity but are returned under the requested id; unknown ids are silently dropped. The response is identical for all viewers and CDN-cacheable.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "description": "Comma-separated raw integer entity ids, 1 to 50 of them (e.g. \"12,844,1032\"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response." + }, + "required": true, + "description": "Comma-separated raw integer entity ids, 1 to 50 of them (e.g. \"12,844,1032\"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response.", + "name": "ids", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityCardsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/{id}": { + "get": { + "tags": [ + "Entities" + ], + "operationId": "get_entity", + "summary": "Get canonical entity metadata", + "description": "Returns the canonical entity envelope for an ent_{n} or numeric id, following merge redirects. For organization and product entities, callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary commercial-intelligence rollup when a brand profile exists.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect." + }, + "required": true, + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityDetailResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/{id}/mentions": { + "get": { + "tags": [ + "Mentions" + ], + "operationId": "list_entity_mentions", + "summary": "List mentions for an entity", + "description": "Cursor-paginated mentions for one entity, newest media first. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect." + }, + "required": true, + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_name", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ] + }, + "required": false, + "name": "sentiment", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "is_appearance", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_from", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_to", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "full" + ] + }, + "required": false, + "name": "details", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentionListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/entities/{id}/recommendations": { + "get": { + "tags": [ + "Recommendations" + ], + "operationId": "list_entity_recommendations", + "summary": "List recommendations for an entity", + "description": "Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect." + }, + "required": true, + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_name", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "ad_read", + "endorsement", + "mention", + "all" + ], + "default": "all" + }, + "required": false, + "name": "mention_class", + "in": "query" + }, + { + "schema": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "maximum": 1, + "default": 0.7 + }, + "required": false, + "name": "min_confidence", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_from", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_to", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "include_disputed", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecommendationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/mentions": { + "get": { + "tags": [ + "Mentions" + ], + "operationId": "list_mentions", + "summary": "Search mentions across media", + "description": "Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "entity_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "entity_name", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ] + }, + "required": false, + "name": "entity_type", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_name", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "positive", + "neutral", + "negative" + ] + }, + "required": false, + "name": "sentiment", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "is_appearance", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_from", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_to", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "full" + ] + }, + "required": false, + "name": "details", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentionListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/recommendations": { + "get": { + "tags": [ + "Recommendations" + ], + "operationId": "list_recommendations", + "summary": "Search recommendations across media", + "description": "Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "entity_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "entity_name", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ] + }, + "required": false, + "name": "entity_type", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "channel_name", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "ad_read", + "endorsement", + "mention", + "all" + ], + "default": "all" + }, + "required": false, + "name": "mention_class", + "in": "query" + }, + { + "schema": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "maximum": 1, + "default": 0.7 + }, + "required": false, + "name": "min_confidence", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_from", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "date_to", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "include_disputed", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecommendationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/feedback": { + "post": { + "tags": [ + "Feedback" + ], + "operationId": "submit_feedback", + "summary": "Submit feedback and corrections for a prior API query", + "description": "Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status \"logged\"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alert rows: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert row id, and every referenced alert row must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with no row to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "recommendations", + "channel_sponsors", + "mentions", + "entities_search", + "entities", + "channels", + "monitor_alert", + "appearances", + "search" + ], + "description": "Feedback type when omitted from the JSON body. Provide type in either location; the body takes precedence." + }, + "required": false, + "description": "Feedback type when omitted from the JSON body. Provide type in either location; the body takes precedence.", + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "string", + "minLength": 1, + "description": "Query being reviewed when omitted from the JSON body. Body query fields override matching query-string fields." + }, + "required": false, + "description": "Query being reviewed when omitted from the JSON body. Body query fields override matching query-string fields.", + "name": "query", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "recommendations", + "channel_sponsors", + "mentions", + "entities_search", + "entities", + "channels", + "monitor_alert", + "appearances", + "search" + ], + "description": "The surface being reviewed. Values: recommendations (/v1/recommendations rows by com_* id; requires a Pro+ plan), channel_sponsors (sponsor entities on a channel; requires a Pro+ plan), mentions (/v1/mentions rows by men_* id), entities_search (/v1/entities/search hits), entities (/v1/entities/lookup and /v1/entities/{id} payloads), channels (/v1/channels/{slug} payloads), monitor_alert (fired alert rows from /v1/monitors/{id}/alerts or /v1/trackers/{id}/alerts; corrections target the alert row id), appearances (person appearance rows), search (rows from /v1/search or /v1/entities/search)." + }, + "query": { + "type": "object", + "additionalProperties": {}, + "description": "The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id." + }, + "endpoint": { + "type": "string", + "maxLength": 500 + }, + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PATCH", + "PUT", + "DELETE" + ] + }, + "request_id": { + "type": "string", + "maxLength": 200 + }, + "result_url": { + "type": "string", + "maxLength": 2000 + }, + "source_url": { + "type": "string", + "maxLength": 2000 + }, + "notes": { + "type": "string", + "maxLength": 4000 + }, + "corrections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Public id of the row being corrected, from the response you received: men_* (mentions, appearances), com_* (recommendations), ent_* (entities, sponsors), or the alert row id (monitor_alert). Omit for missed_alert and missing_result corrections, which have no row to target." + }, + "mention_class": { + "type": "string", + "enum": [ + "ad_read", + "endorsement", + "mention" + ] + }, + "reason": { + "type": "string", + "enum": [ + "false_positive_ad_read", + "false_positive_endorsement", + "missed_ad_read", + "missed_endorsement", + "wrong_classification", + "wrong_entity", + "other" + ] + }, + "issue_type": { + "type": "string", + "enum": [ + "wrong_entity_type", + "wrong_entity", + "duplicate_entity", + "merge_suggestion", + "missing_result", + "stale_metadata", + "wrong_classification", + "bad_ranking", + "false_positive_alert", + "wrong_media", + "wrong_timestamp", + "duplicate_alert", + "missed_alert", + "delivery_issue", + "person_not_present", + "wrong_person", + "wrong_appearance_role", + "other" + ], + "description": "Issue classification for the correction. Entity-family values: wrong_entity_type (right entity, wrong type), wrong_entity (the row points at the wrong canonical entity), duplicate_entity (results split across variants of the same entity), merge_suggestion (propose the canonical merge for split variants), missing_result (a result you know should exist is absent), stale_metadata (name/website/channel metadata is outdated), wrong_classification (class-level error on a commercial row), bad_ranking (duplicates or aliases ranking above the canonical entity). monitor_alert values: false_positive_alert (the alert should not have fired), wrong_media (fired against the wrong video), wrong_timestamp (fired at the wrong position in the video), duplicate_alert (the same occurrence fired more than once), missed_alert (an expectation: an alert that should have fired but did not; no row to target), delivery_issue (the delivery itself was wrong: wrong channel, not received). appearances values: person_not_present (the person does not appear in the media), wrong_person (the appearance is attributed to the wrong person), wrong_appearance_role (right person, wrong role, e.g. guest vs host). other (escape hatch; detail in notes)." + }, + "suggested_change": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeSuggestionChange" + }, + { + "$ref": "#/components/schemas/WrongEntityChange" + }, + { + "$ref": "#/components/schemas/WrongEntityTypeChange" + }, + { + "$ref": "#/components/schemas/MissingResultChange" + }, + { + "$ref": "#/components/schemas/WrongClassificationChange" + }, + { + "$ref": "#/components/schemas/StaleMetadataChange" + }, + { + "$ref": "#/components/schemas/BadRankingChange" + }, + { + "$ref": "#/components/schemas/MissedAlertChange" + }, + { + "$ref": "#/components/schemas/DeliveryIssueChange" + }, + { + "$ref": "#/components/schemas/FreeformSuggestedChange" + } + ], + "description": "Your concrete proposed fix, shaped by issue_type: merge_suggestion → MergeSuggestionChange, wrong_entity/wrong_person → WrongEntityChange, wrong_entity_type → WrongEntityTypeChange, missing_result → MissingResultChange, wrong_classification → WrongClassificationChange, stale_metadata → StaleMetadataChange, bad_ranking → BadRankingChange, missed_alert → MissedAlertChange, delivery_issue → DeliveryIssueChange. Unknown keys are accepted and logged verbatim; only non-object values are rejected. Omit suggested_change entirely when you do not have a concrete fix." + }, + "notes": { + "type": "string", + "maxLength": 2000 + } + } + }, + "maxItems": 100 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeedbackResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/feedback/{feedback_id}": { + "get": { + "tags": [ + "Feedback" + ], + "operationId": "get_feedback", + "summary": "Read back a feedback submission and its review status", + "description": "Returns the submission (id, type, query, notes, created_at) plus its per-correction rows, each with a review status in the public vocabulary: pending_review, needs_information, accepted, accepted_with_changes, rejected, withdrawn, applied, reverted (accepted means a reviewer agreed; applied means the change is live in the index). Only the submitting user's keys can read a submission; unknown ids and other users' submissions both return 404 (never 403).", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The feedback submission id POST /v1/feedback returned." + }, + "required": true, + "description": "The feedback submission id POST /v1/feedback returned.", + "name": "feedback_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeedbackReadbackResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{channel_id}/sponsors": { + "get": { + "tags": [ + "Recommendations" + ], + "operationId": "list_channel_sponsors", + "summary": "List recurring channel sponsors", + "description": "Rollup of recurring sponsors for a YouTube channel, ordered by ad read count. On a Pro+ plan the full list is served and min_ad_reads (default 3), status, and limit apply. Every other plan receives the free slice an anonymous visitor sees on arcmira.com, with meta.total naming the true count and access naming the gate; passing min_ad_reads, status, or limit on such a plan is refused with filter_requires_paid. Pass src=mcp-tool only from the Arcmira MCP server.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube channel id, the UC... form." + }, + "required": true, + "description": "YouTube channel id, the UC... form.", + "name": "channel_id", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid." + }, + "required": false, + "description": "Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid.", + "name": "min_ad_reads", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "active", + "lapsed", + "ended", + "uncertain" + ], + "description": "Filter against the curated known-advertisers dataset. Pro+ only." + }, + "required": false, + "description": "Filter against the curated known-advertisers dataset. Pro+ only.", + "name": "status", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "description": "Sponsors to return. Default 100. Pro+ only; other plans receive the free slice." + }, + "required": false, + "description": "Sponsors to return. Default 100. Pro+ only; other plans receive the free slice.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelSponsorsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/search": { + "get": { + "tags": [ + "Search" + ], + "operationId": "search_transcripts", + "summary": "Retrieve spoken transcript slices for one topic", + "description": "Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to mention, recommendation_sponsored or recommendation_organic passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per chunk returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 2, + "description": "One topic or phrase. Do not concatenate unrelated names; make one call per topic." + }, + "required": true, + "description": "One topic or phrase. Do not concatenate unrelated names; make one call per topic.", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve." + }, + "required": false, + "description": "Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.", + "name": "channel_ids", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Alias of channel_ids for code-mode clients; the union of both is the scope." + }, + "required": false, + "description": "Alias of channel_ids for code-mode clients; the union of both is the scope.", + "name": "channel", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve." + }, + "required": false, + "description": "Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.", + "name": "entity_ids", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve." + }, + "required": false, + "description": "Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.", + "name": "about", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve." + }, + "required": false, + "description": "Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.", + "name": "by", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk." + }, + "required": false, + "description": "Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk.", + "name": "kind", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened." + }, + "required": false, + "description": "ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened.", + "name": "published_after", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "ISO date. Only media published before this day." + }, + "required": false, + "description": "ISO date. Only media published before this day.", + "name": "published_before", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "arcmira_premium", + "creator_captions", + "third_party_quick" + ], + "description": "Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid." + }, + "required": false, + "description": "Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid.", + "name": "source", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 20, + "default": 5, + "description": "Chunks to return, 1 to 20. Default 5." + }, + "required": false, + "description": "Chunks to return, 1 to 20. Default 5.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptSearchResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + }, + "503": { + "description": "search_unavailable. Search is unavailable right now. Retry-After carries the wait; nothing was charged.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/entities/{id}/momentum": { + "get": { + "tags": [ + "Entities" + ], + "operationId": "get_entity_momentum", + "summary": "Spoken-web heat for one entity", + "description": "Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect." + }, + "required": true, + "description": "Entity id, ent_{n} or the numeric id. Merged ids follow their redirect.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityMomentumResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{channel_id}/coverage": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "get_channel_coverage", + "summary": "What the index holds for a channel", + "description": "How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube channel id, the UC... form." + }, + "required": true, + "description": "YouTube channel id, the UC... form.", + "name": "channel_id", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelCoverageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{channel_id}/videos": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_videos", + "summary": "Newest indexed videos of a channel", + "description": "The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube channel id, the UC... form." + }, + "required": true, + "description": "YouTube channel id, the UC... form.", + "name": "channel_id", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10, + "description": "Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode." + }, + "required": false, + "description": "Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor." + }, + "required": false, + "description": "Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "ISO date. Only videos published on or after this day." + }, + "required": false, + "description": "ISO date. Only videos published on or after this day.", + "name": "published_after", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "ISO date. Only videos published before this day." + }, + "required": false, + "description": "ISO date. Only videos published before this day.", + "name": "published_before", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelVideosResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/mentions/counts": { + "get": { + "tags": [ + "Mentions" + ], + "operationId": "count_mentions", + "summary": "Ranked catalog counts of who a set of channels mention", + "description": "A small ranked table of entity and channel counts, all-time unless published_after is set. Pass channel_ids for what shows talk about and entity_types to match the question (topic for subjects, person for guests, organization,product for brands). Pass video_ids with one id from GET /v1/channels/{channel_id}/videos for what a single episode mentions. Two or more channel_ids also return shared, the entities on more than one of them ranked by the smallest per-channel count, which is true overlap. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per table row returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them." + }, + "required": false, + "description": "Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them.", + "name": "channel_ids", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities." + }, + "required": false, + "description": "Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities.", + "name": "entity_ids", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos." + }, + "required": false, + "description": "Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos.", + "name": "video_ids", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate." + }, + "required": false, + "description": "Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate.", + "name": "entity_types", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mentions", + "appearances", + "both" + ], + "default": "mentions", + "description": "mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions." + }, + "required": false, + "description": "mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened." + }, + "required": false, + "description": "ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened.", + "name": "published_after", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "ISO date. Only media published before this day." + }, + "required": false, + "description": "ISO date. Only media published before this day.", + "name": "published_before", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 40, + "default": 20, + "description": "Rows in the ranked table, 1 to 40. Default 20." + }, + "required": false, + "description": "Rows in the ranked table, 1 to 40. Default 20.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentionCountsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}": { + "get": { + "tags": [ + "People" + ], + "operationId": "get_person", + "summary": "Get a person entity page", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PersonPageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}/appearances": { + "get": { + "tags": [ + "Appearances" + ], + "operationId": "list_person_appearances", + "summary": "List person appearances", + "description": "Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PersonAppearanceListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}/topics": { + "get": { + "tags": [ + "People" + ], + "operationId": "list_person_topics", + "summary": "List topics related to a person", + "description": "The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityTopicListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}/people": { + "get": { + "tags": [ + "People" + ], + "operationId": "list_person_people", + "summary": "List people related to a person", + "description": "The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityPeopleListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}/organizations": { + "get": { + "tags": [ + "People" + ], + "operationId": "list_person_organizations", + "summary": "List organizations related to a person", + "description": "The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityOrganizationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}/products": { + "get": { + "tags": [ + "People" + ], + "operationId": "list_person_products", + "summary": "List products related to a person", + "description": "The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityProductListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/people/{slug}/channels": { + "get": { + "tags": [ + "People" + ], + "operationId": "list_person_channels", + "summary": "List channels related to a person", + "description": "The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityChannelListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/topics/{slug}": { + "get": { + "tags": [ + "Topics" + ], + "operationId": "get_topic", + "summary": "Get a topic entity page", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TopicPageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/topics/{slug}/topics": { + "get": { + "tags": [ + "Topics" + ], + "operationId": "list_topic_topics", + "summary": "List topics related to a topic", + "description": "The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityTopicListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/topics/{slug}/people": { + "get": { + "tags": [ + "Topics" + ], + "operationId": "list_topic_people", + "summary": "List people related to a topic", + "description": "The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityPeopleListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/topics/{slug}/organizations": { + "get": { + "tags": [ + "Topics" + ], + "operationId": "list_topic_organizations", + "summary": "List organizations related to a topic", + "description": "The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityOrganizationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/topics/{slug}/products": { + "get": { + "tags": [ + "Topics" + ], + "operationId": "list_topic_products", + "summary": "List products related to a topic", + "description": "The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityProductListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/topics/{slug}/channels": { + "get": { + "tags": [ + "Topics" + ], + "operationId": "list_topic_channels", + "summary": "List channels related to a topic", + "description": "The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityChannelListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/organizations/{slug}": { + "get": { + "tags": [ + "Organizations" + ], + "operationId": "get_organization", + "summary": "Get a organization entity page", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationPageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/organizations/{slug}/topics": { + "get": { + "tags": [ + "Organizations" + ], + "operationId": "list_organization_topics", + "summary": "List topics related to a organization", + "description": "The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityTopicListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/organizations/{slug}/people": { + "get": { + "tags": [ + "Organizations" + ], + "operationId": "list_organization_people", + "summary": "List people related to a organization", + "description": "The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityPeopleListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/organizations/{slug}/organizations": { + "get": { + "tags": [ + "Organizations" + ], + "operationId": "list_organization_organizations", + "summary": "List organizations related to a organization", + "description": "The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityOrganizationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/organizations/{slug}/products": { + "get": { + "tags": [ + "Organizations" + ], + "operationId": "list_organization_products", + "summary": "List products related to a organization", + "description": "The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityProductListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/organizations/{slug}/channels": { + "get": { + "tags": [ + "Organizations" + ], + "operationId": "list_organization_channels", + "summary": "List channels related to a organization", + "description": "The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityChannelListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/products/{slug}": { + "get": { + "tags": [ + "Products" + ], + "operationId": "get_product", + "summary": "Get a product entity page", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductPageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/products/{slug}/topics": { + "get": { + "tags": [ + "Products" + ], + "operationId": "list_product_topics", + "summary": "List topics related to a product", + "description": "The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityTopicListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/products/{slug}/people": { + "get": { + "tags": [ + "Products" + ], + "operationId": "list_product_people", + "summary": "List people related to a product", + "description": "The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityPeopleListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/products/{slug}/organizations": { + "get": { + "tags": [ + "Products" + ], + "operationId": "list_product_organizations", + "summary": "List organizations related to a product", + "description": "The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityOrganizationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/products/{slug}/products": { + "get": { + "tags": [ + "Products" + ], + "operationId": "list_product_products", + "summary": "List products related to a product", + "description": "The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityProductListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/products/{slug}/channels": { + "get": { + "tags": [ + "Products" + ], + "operationId": "list_product_channels", + "summary": "List channels related to a product", + "description": "The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityChannelListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "get_channel", + "summary": "Get a channel entity page", + "description": "Channel pages include a recommendations_summary teaser: sponsor_count for all callers; top_sponsors additionally requires a Pro+ plan.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelPageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}/topics": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_topics", + "summary": "List topics related to a channel", + "description": "The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityTopicListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}/people": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_people", + "summary": "List people related to a channel", + "description": "The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityPeopleListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}/organizations": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_organizations", + "summary": "List organizations related to a channel", + "description": "The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityOrganizationListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}/products": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_products", + "summary": "List products related to a channel", + "description": "The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityProductListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}/channels": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_channels", + "summary": "List channels related to a channel", + "description": "The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityChannelListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/channels/{slug}/guests": { + "get": { + "tags": [ + "Channels" + ], + "operationId": "list_channel_guests", + "summary": "List channel guests", + "description": "People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it." + }, + "required": true, + "description": "The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it.", + "name": "slug", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + }, + "required": false, + "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description)." + }, + "required": false, + "description": "Substring filter over the row's text columns (e.g. video title, channel name, description).", + "name": "q", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns)." + }, + "required": false, + "description": "Restrict the q filter to one column. Default \"any\" (all searchable columns).", + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\")." + }, + "required": false, + "description": "Sort key. Rows default to newest first; supported values vary by list (e.g. \"date\", \"channel\").", + "name": "sort", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort direction. Default desc." + }, + "required": false, + "description": "Sort direction. Default desc.", + "name": "order", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "appearances", + "mentions" + ], + "description": "Person relationship lens: guest appearances or inbound mentions." + }, + "required": false, + "description": "Person relationship lens: guest appearances or inbound mentions.", + "name": "mode", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "For person appearances, true lists guest episodes and false lists inbound mentions." + }, + "required": false, + "description": "For person appearances, true lists guest episodes and false lists inbound mentions.", + "name": "is_appearance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelGuestListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/monitors": { + "get": { + "tags": [ + "Monitors" + ], + "operationId": "list_monitors", + "summary": "List monitors", + "description": "All monitors for the account with tracker counts, alert counts for the current calendar month, and Slack display metadata. Single page, no pagination.", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "post": { + "tags": [ + "Monitors" + ], + "operationId": "create_monitor", + "summary": "Create monitor", + "description": "Creating with notifyWebhook: true and a webhookUrl enables HMAC-signed webhook delivery and returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. All subsequent reads expose only webhookSecretSet and webhookSecretHint.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Display name (1-100 characters). Required on create." + }, + "notifyEmails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "maxItems": 20, + "description": "Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []." + }, + "notifyFrequency": { + "type": "string", + "enum": [ + "realtime", + "hourly", + "daily" + ], + "description": "Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent." + }, + "digestDay": { + "type": "string", + "description": "Digest day of week. Default \"monday\". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies." + }, + "digestTime": { + "type": "string", + "description": "Digest send hour as HH:MM (account timezone). Default \"09:00\". Applies to daily digests." + }, + "notifyWebhook": { + "type": "boolean", + "description": "Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter." + }, + "webhookUrl": { + "type": "string", + "description": "Destination URL for webhook alert deliveries." + }, + "notifySlack": { + "type": "boolean", + "description": "Enable Slack delivery. Requires a Slack integration connected in the dashboard." + }, + "slackIntegrationId": { + "type": "string", + "description": "Slack integration id from the dashboard OAuth flow." + }, + "slackChannelId": { + "type": "string", + "description": "Slack channel id to deliver to." + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorMutationResponse" + } + } + } + }, + "201": { + "description": "Monitor created. When this request enabled webhook signing, monitor.webhookSecret is present and recoverable by retrying with the original Idempotency-Key during its recovery window.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorMutationResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/monitors/{id}": { + "patch": { + "tags": [ + "Monitors" + ], + "operationId": "update_monitor", + "summary": "Update monitor", + "description": "A PATCH that newly enables webhook signing (turns notifyWebhook on, or sets a webhookUrl where no secret existed before) returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Unrelated PATCHes expose only webhookSecretSet and webhookSecretHint. PATCHing notifyWebhook: true also re-enables a webhook that was auto-disabled after repeated failures and resets its failure counter.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Monitor id." + }, + "required": true, + "description": "Monitor id.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Display name (1-100 characters). Required on create." + }, + "notifyEmails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "maxItems": 20, + "description": "Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []." + }, + "notifyFrequency": { + "type": "string", + "enum": [ + "realtime", + "hourly", + "daily" + ], + "description": "Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent." + }, + "digestDay": { + "type": "string", + "description": "Digest day of week. Default \"monday\". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies." + }, + "digestTime": { + "type": "string", + "description": "Digest send hour as HH:MM (account timezone). Default \"09:00\". Applies to daily digests." + }, + "notifyWebhook": { + "type": "boolean", + "description": "Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter." + }, + "webhookUrl": { + "type": "string", + "description": "Destination URL for webhook alert deliveries." + }, + "notifySlack": { + "type": "boolean", + "description": "Enable Slack delivery. Requires a Slack integration connected in the dashboard." + }, + "slackIntegrationId": { + "type": "string", + "description": "Slack integration id from the dashboard OAuth flow." + }, + "slackChannelId": { + "type": "string", + "description": "Slack channel id to deliver to." + }, + "isPaused": { + "type": "boolean", + "description": "Paused monitors accept config changes but do not deliver; alerts that would have fired are not queued." + }, + "isCollapsed": { + "type": "boolean", + "description": "Dashboard display state." + }, + "sortOrder": { + "type": "integer", + "description": "Dashboard sort position." + } + }, + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorMutationResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "delete": { + "tags": [ + "Monitors" + ], + "operationId": "delete_monitor", + "summary": "Delete monitor", + "description": "Deletes the monitor AND every tracker inside it (trackersDeleted reports how many). Cannot be undone. Retrying with the original Idempotency-Key returns the original deleted count without deleting again.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Monitor id." + }, + "required": true, + "description": "Monitor id.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorDeleteResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/monitors/{id}/webhook-secret/rotate": { + "post": { + "tags": [ + "Monitors" + ], + "operationId": "rotate_monitor_webhook_secret", + "summary": "Rotate the monitor webhook signing secret", + "description": "Generates a new signing secret and returns it in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Zero-downtime overlap: the previous secret remains valid until previousSecretExpiresAt (24 hours); during the window every delivery carries an additional X-Arcmira-Signature-Previous header computed with the old secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. After the window the old secret is dropped and the extra header disappears. Rotating again during the window replaces the previous secret and resets the window. Requires a configured webhook (webhookUrl set); otherwise 409 with code webhook_not_configured. Auto-disable interplay: rotation resets webhook_failures but never re-enables a webhook that was auto-disabled after repeated failures; to resume delivery, also PATCH the monitor with notifyWebhook: true. Requires the monitors:write scope.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Monitor id." + }, + "required": true, + "description": "Monitor id.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookSecretRotateResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent. The route also answers 409 for a duplicate or a failed precondition, named in its description.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/monitors/{id}/trackers": { + "get": { + "tags": [ + "Monitors" + ], + "operationId": "list_monitor_trackers", + "summary": "List monitor trackers", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Monitor id." + }, + "required": true, + "description": "Monitor id.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorTrackersResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "post": { + "tags": [ + "Monitors" + ], + "operationId": "add_monitor_trackers", + "summary": "Add trackers to monitor", + "description": "Attaches EXISTING trackers to the monitor by id ({ trackerIds: [\"trk_...\"] }). It does not create trackers: create them first via POST /v1/trackers, then attach. Attached trackers use the monitor's delivery settings. Supply 1 to 90 IDs. Duplicate IDs count once. Every ID must belong to the account; a missing or foreign ID returns tracker_not_found and none are attached. attachedCount reports the unique attached count.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Monitor id." + }, + "required": true, + "description": "Monitor id.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "trackerIds": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 90, + "description": "Ids of existing trackers (\"trk_...\") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached." + } + }, + "required": [ + "trackerIds" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitorAddTrackersResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/monitors/{id}/alerts": { + "get": { + "tags": [ + "Monitors" + ], + "operationId": "list_monitor_alerts", + "summary": "List recent monitor alerts", + "description": "The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id (\"ent_{n}\") and mention_id (\"men_{n}\") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Monitor id." + }, + "required": true, + "description": "Monitor id.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "required": false, + "name": "n", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlertListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/trackers": { + "get": { + "tags": [ + "Trackers" + ], + "operationId": "list_trackers", + "summary": "List trackers", + "description": "All trackers for the account, newest first, with per-channel delivery counts for the current billing period. Single page, no pagination.", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrackerListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "post": { + "tags": [ + "Trackers" + ], + "operationId": "create_tracker", + "summary": "Create tracker", + "description": "Creates a standalone tracker watching one entity (name + type, resolved with the same entity resolution Search uses). Attach it to a monitor afterwards via POST /v1/monitors/{id}/trackers. Creating a duplicate (same entity name + type) returns 409 with the existingId.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entityName": { + "type": "string", + "minLength": 1, + "description": "The entity name to resolve and watch. Required on create. Creating a duplicate (same name + type) returns 409 with the existingId." + }, + "entityType": { + "type": "string", + "enum": [ + "person", + "organization", + "product", + "topic", + "channel" + ], + "description": "Entity type of the tracked entity. Required on create." + }, + "displayName": { + "type": "string", + "description": "Optional label shown in alerts and the dashboard." + }, + "notifyEmail": { + "type": "boolean", + "description": "Per-tracker email delivery. Default true." + }, + "notifyWebhook": { + "type": "boolean", + "description": "Per-tracker webhook delivery override. Paid plans only." + }, + "notifySlack": { + "type": "boolean", + "description": "Per-tracker Slack delivery override. Paid plans only." + }, + "webhookUrl": { + "type": "string", + "description": "Per-tracker webhook destination override (http/https)." + }, + "slackChannelId": { + "type": "string", + "description": "Per-tracker Slack channel override." + }, + "slackIntegrationId": { + "type": "string", + "description": "Per-tracker Slack integration override." + }, + "personMatchMode": { + "type": "string", + "enum": [ + "mentions", + "appearances", + "both" + ], + "description": "Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill." + }, + "filters": { + "type": "object", + "additionalProperties": {}, + "description": "Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching." + } + }, + "required": [ + "entityName", + "entityType" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrackerMutationResponse" + } + } + } + }, + "201": { + "description": "Tracker created", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrackerMutationResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent. The route also answers 409 for a duplicate or a failed precondition, named in its description.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/trackers/{id}": { + "patch": { + "tags": [ + "Trackers" + ], + "operationId": "update_tracker", + "summary": "Update tracker", + "description": "Partial update: send only the fields to change. The tracked entity itself (entityName/entityType) is immutable; delete and recreate to watch a different entity.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Tracker id, trk_ form." + }, + "required": true, + "description": "Tracker id, trk_ form.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "Optional label shown in alerts and the dashboard." + }, + "notifyEmail": { + "type": "boolean", + "description": "Per-tracker email delivery. Default true." + }, + "notifyWebhook": { + "type": "boolean", + "description": "Per-tracker webhook delivery override. Paid plans only." + }, + "notifySlack": { + "type": "boolean", + "description": "Per-tracker Slack delivery override. Paid plans only." + }, + "webhookUrl": { + "type": "string", + "description": "Per-tracker webhook destination override (http/https)." + }, + "slackChannelId": { + "type": "string", + "description": "Per-tracker Slack channel override." + }, + "slackIntegrationId": { + "type": "string", + "description": "Per-tracker Slack integration override." + }, + "personMatchMode": { + "type": "string", + "enum": [ + "mentions", + "appearances", + "both" + ], + "description": "Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill." + }, + "filters": { + "type": "object", + "additionalProperties": {}, + "description": "Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching." + }, + "paused": { + "type": "boolean", + "description": "Pause or resume the tracker. Paused trackers stop producing alerts; there is no backfill for the paused window." + } + }, + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrackerMutationResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "delete": { + "tags": [ + "Trackers" + ], + "operationId": "delete_tracker", + "summary": "Delete tracker", + "description": "Deletes the tracker. Cannot be undone.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Tracker id, trk_ form." + }, + "required": true, + "description": "Tracker id, trk_ form.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/trackers/{id}/alerts": { + "get": { + "tags": [ + "Trackers" + ], + "operationId": "list_tracker_alerts", + "summary": "List recent tracker alerts", + "description": "The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id (\"ent_{n}\") and mention_id (\"men_{n}\") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Tracker id, trk_ form." + }, + "required": true, + "description": "Tracker id, trk_ form.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "required": false, + "name": "n", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlertListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/team/members": { + "get": { + "tags": [ + "Team" + ], + "operationId": "list_team_members", + "summary": "List team members", + "description": "Active members of the team the key is scoped to, with role and seat type, earliest join first. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required).", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamMembersResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/team/spend": { + "get": { + "tags": [ + "Team" + ], + "operationId": "get_team_spend", + "summary": "Per-member spend and rows used this period", + "description": "Account-wide rows consumed and on-demand overage spend for every active member in the current period. Totals include personal-key use and activity before joining; they are not a team-attributed invoice. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required).", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamSpendResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/team/usage-events": { + "get": { + "tags": [ + "Team" + ], + "operationId": "list_team_usage_events", + "summary": "List team usage events", + "description": "Account usage log across every active team member, including personal-key use and activity before joining. Ordered by created_at and id descending, bounded to a 90-day look-back. Continuation preserves the first page's time window and excludes subsequently inserted events, including backfills. Removed members and deleted events disappear during traversal; this is not a historical membership snapshot. Cursors expire after 24 hours and bind the team, caller, days and limit; invalid or changed-query cursors return invalid_cursor rather than restarting. The aggregated analytics chart data is not exposed on this API (Enterprise). Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required).", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "Events per page, 1 to 100." + }, + "required": false, + "description": "Events per page, 1 to 100.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Opaque cursor from a previous page's next_cursor." + }, + "required": false, + "description": "Opaque cursor from a previous page's next_cursor.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "default": 30, + "description": "Look-back window in days, bounded at 90." + }, + "required": false, + "description": "Look-back window in days, bounded at 90.", + "name": "days", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamUsageEventsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}": { + "get": { + "tags": [ + "Transcripts" + ], + "operationId": "get_transcript", + "summary": "Get a video transcript", + "description": "Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "captions", + "premium" + ], + "description": "captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings." + }, + "required": false, + "description": "captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings.", + "name": "quality", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Comma-separated caption language priority list, at most 5, tried in order (e.g. \"de,en\"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers." + }, + "required": false, + "description": "Comma-separated caption language priority list, at most 5, tried in order (e.g. \"de,en\"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers.", + "name": "language", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "description": "false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true." + }, + "required": false, + "description": "false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true.", + "name": "timestamps", + "in": "query" + }, + { + "schema": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge." + }, + "required": false, + "description": "Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge.", + "name": "start", + "in": "query" + }, + { + "schema": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Window end in seconds, greater than start and no greater than the video duration. Send start and end together." + }, + "required": false, + "description": "Window end in seconds, greater than start and no greater than the video duration. Send start and end together.", + "name": "end", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "description": "Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query." + }, + "required": false, + "description": "Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query.", + "name": "refresh", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptResponse" + } + } + } + }, + "202": { + "description": "Owned purchase is pending; no transcript content or charge.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptPending" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + }, + "503": { + "description": "transcript_fetching. The caption track is being fetched now. Retry-After and retry_after_seconds carry the wait; nothing was charged.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/transcripts/{video_id}/quote": { + "get": { + "tags": [ + "Transcripts" + ], + "operationId": "quote_transcription", + "summary": "Quote a whole-video Premium purchase", + "description": "Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptPurchaseQuote" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/videos/{video_id}/captions": { + "get": { + "tags": [ + "Transcripts" + ], + "operationId": "get_video_captions", + "summary": "List the caption tracks a video offers", + "description": "Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoCaptionsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + }, + "503": { + "description": "transcript_fetching. The listing is cold and is being fetched now. Retry-After and retry_after_seconds carry the wait; this route never charges rows.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/transcriptions": { + "post": { + "tags": [ + "Transcriptions" + ], + "operationId": "submit_transcription", + "summary": "Submit a YouTube video for transcription", + "description": "Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "Idempotency-Key", + "in": "header", + "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "max_on_demand_cents": { + "type": "number", + "minimum": 0, + "default": 0, + "description": "Maximum new monetary on-demand charge in cents. Omit to authorize none." + }, + "max_rows": { + "type": "integer", + "minimum": 0, + "maximum": 3600, + "description": "Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote." + }, + "videoId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{11}$", + "description": "YouTube video id (11 characters). Either videoId or url is required." + }, + "url": { + "type": "string", + "format": "uri", + "description": "A YouTube watch/short/live URL. Either videoId or url is required." + } + }, + "required": [ + "max_rows" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "An existing in-flight or already-satisfied request was returned (existing: true)", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptionSubmitResponse" + } + } + } + }, + "201": { + "description": "Purchase ready", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptionSubmitResponse" + } + } + } + }, + "202": { + "description": "Purchase funded; generation pending", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptionSubmitResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "402": { + "$ref": "#/components/responses/QuotaExceeded" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Duration unavailable or video too long (12h cap)", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "get": { + "tags": [ + "Transcriptions" + ], + "operationId": "list_transcriptions", + "summary": "List your transcription requests", + "description": "Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{11}$", + "description": "Filter to your requests for one video." + }, + "required": false, + "description": "Filter to your requests for one video.", + "name": "video_id", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Requests per page, from 1 to 100. Default 20." + }, + "required": false, + "description": "Requests per page, from 1 to 100. Default 20.", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Signed continuation from next_cursor. Keep the same filter, limit and credential." + }, + "required": false, + "description": "Signed continuation from next_cursor. Keep the same filter, limit and credential.", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptionListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcriptions/{id}": { + "get": { + "tags": [ + "Transcriptions" + ], + "operationId": "get_transcription", + "summary": "Poll transcription status", + "description": "Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Transcription request id, the UUID POST /v1/transcriptions returned." + }, + "required": true, + "description": "Transcription request id, the UUID POST /v1/transcriptions returned.", + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp-tool" + ], + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client." + }, + "required": false, + "description": "The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client.", + "name": "src", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Retry-After": { + "$ref": "#/components/headers/Retry-After" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptionRequest" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/videos/{video_id}/corrections": { + "post": { + "tags": [ + "Corrections" + ], + "operationId": "submit_correction", + "summary": "Submit a transcript correction", + "description": "Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "line_edit", + "speaker_reassign", + "speaker_identify", + "add_person", + "entity_tag", + "segment_rewrite" + ] + }, + "seq": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value." + }, + "revision": { + "type": "string", + "description": "The revision from the Premium transcript read this correction was made against. Required for every kind. Speaker ids in the payload are the speakers[].id values of that read." + }, + "anchor": { + "type": "object", + "properties": { + "segmentIndex": { + "type": "integer", + "minimum": 0 + }, + "contentHash": { + "type": "string", + "minLength": 1, + "description": "djb2 hash of the covered segment text (joined with \\n for ranges), computed against your projected view of the transcript." + } + }, + "required": [ + "segmentIndex", + "contentHash" + ], + "description": "Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite." + }, + "payload": { + "type": "object", + "additionalProperties": {}, + "description": "Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex." + } + }, + "required": [ + "kind", + "payload" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CorrectionAcceptedResponse" + } + } + } + }, + "201": { + "description": "Correction accepted ({ ok, kind, result }; result.id withdraws it later)", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CorrectionAcceptedResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent. The route also answers 409 for a duplicate or a failed precondition, named in its description.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "412": { + "description": "Sequence mismatch ({ error, expectedSeq }). Refetch, rebase, resend.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CorrectionSeqMismatchResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/corrections/speaker-edits/{id}": { + "delete": { + "tags": [ + "Corrections" + ], + "operationId": "withdraw_speaker_edit", + "summary": "Withdraw your pending speaker reassign or split", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The pending row id, as result.id of the response that accepted it." + }, + "required": true, + "description": "The pending row id, as result.id of the response that accepted it.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Withdrawn", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawnResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/corrections/entity-tags/{id}": { + "delete": { + "tags": [ + "Corrections" + ], + "operationId": "withdraw_entity_tag", + "summary": "Withdraw your pending entity tag", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The pending row id, as result.id of the response that accepted it." + }, + "required": true, + "description": "The pending row id, as result.id of the response that accepted it.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Withdrawn", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawnResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/corrections/segment-rewrites/{id}": { + "delete": { + "tags": [ + "Corrections" + ], + "operationId": "withdraw_segment_rewrite", + "summary": "Withdraw your pending segment rewrite", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "The pending row id, as result.id of the response that accepted it." + }, + "required": true, + "description": "The pending row id, as result.id of the response that accepted it.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Withdrawn", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawnResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}/edits": { + "post": { + "tags": [ + "Corrections" + ], + "operationId": "submit_transcript_edit", + "summary": "Submit a transcript line-text correction", + "description": "Purpose-built wrapper for the line_edit kind. The edit is pending review: visible to you immediately (returned in the transcript GET `edits[]`), applied for everyone once approved. Free (0 rows), attributed to your API key.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "segmentIndex": { + "type": "integer", + "minimum": 0 + }, + "originalText": { + "type": "string", + "description": "The current segment text you are correcting (guards against applying to a changed segment)." + }, + "correctedText": { + "type": "string", + "minLength": 1, + "maxLength": 2000 + }, + "revision": { + "type": "string", + "description": "The revision from the Premium transcript read." + } + }, + "required": [ + "segmentIndex", + "originalText", + "correctedText" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Edit accepted, pending review", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptEditSubmittedResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent. The route also answers 409 for a duplicate or a failed precondition, named in its description.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}/edits/{id}": { + "delete": { + "tags": [ + "Corrections" + ], + "operationId": "withdraw_transcript_edit", + "summary": "Withdraw your pending line edit", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "The pending row id, as result.id of the response that accepted it." + }, + "required": true, + "description": "The pending row id, as result.id of the response that accepted it.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Withdrawn", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawnResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}/speakers": { + "post": { + "tags": [ + "Corrections" + ], + "operationId": "identify_transcript_speaker", + "summary": "Identify a diarized speaker as a person", + "description": "Links a diarization speaker id to a person entity (or proposes a new person via `name`). Creates a community-attributed appearance immediately. It shows on the person page right away, flagged pending review; reviewers can revert it. Free (0 rows).", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "speakerId": { + "type": "integer", + "minimum": 0, + "description": "A speakers[].id from the Premium transcript read that revision names." + }, + "entityId": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Existing person entity id. Either entityId or name is required." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "Propose a person not in the index yet. Reuses an existing same-name person or creates a provisional one. Either entityId or name is required." + }, + "revision": { + "type": "string", + "description": "The revision of the transcript read speakerId came from. Required." + } + }, + "required": [ + "speakerId" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Identification accepted, pending review", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SpeakerIdentificationSubmittedResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent. The route also answers 409 for a duplicate or a failed precondition, named in its description.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}/speakers/{id}": { + "delete": { + "tags": [ + "Corrections" + ], + "operationId": "withdraw_speaker_identification", + "summary": "Withdraw your pending speaker identification", + "description": "Withdrawing also removes the community-attributed appearance the identification created.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "The pending row id, as result.id of the response that accepted it." + }, + "required": true, + "description": "The pending row id, as result.id of the response that accepted it.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Withdrawn", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawnResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}/merges": { + "post": { + "tags": [ + "Corrections" + ], + "operationId": "submit_video_merge", + "summary": "Submit a video-level entity merge", + "description": "Asserts that a name in this video refers to a specific entity, for misattributed name mentions in one video (e.g. a first-name-only mention resolved to the wrong entity). Optionally respells the transcript text via `replaceWith`. Pending review; applied optimistically for you. Mentions of the same name in other videos are untouched. Free (0 rows).", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "Idempotency-Key", + "in": "header", + "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sourceName": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "The name as it appears in this video (e.g. a first-name-only mention)." + }, + "targetEntityId": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "The canonical entity these mentions actually refer to." + }, + "replaceWith": { + "type": "string", + "maxLength": 120, + "description": "Optional respelling applied to the transcript text (e.g. \"Imad\" → \"Emad\")." + }, + "revision": { + "type": "string" + } + }, + "required": [ + "sourceName", + "targetEntityId" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Merge accepted, pending review", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + }, + "Idempotency-Replayed": { + "$ref": "#/components/headers/Idempotency-Replayed" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoMergeSubmittedResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + }, + "get": { + "tags": [ + "Corrections" + ], + "operationId": "list_video_merges", + "summary": "List your pending video merges", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoMergeListResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + }, + "/v1/transcripts/{video_id}/merges/{id}": { + "delete": { + "tags": [ + "Corrections" + ], + "operationId": "withdraw_video_merge", + "summary": "Withdraw your pending video merge", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "YouTube video id, 11 characters." + }, + "required": true, + "description": "YouTube video id, 11 characters.", + "name": "video_id", + "in": "path" + }, + { + "schema": { + "type": "string", + "description": "The pending row id, as result.id of the response that accepted it." + }, + "required": true, + "description": "The pending row id, as result.id of the response that accepted it.", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Withdrawn", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + }, + "RateLimit-Limit": { + "$ref": "#/components/headers/RateLimit-Limit" + }, + "RateLimit-Remaining": { + "$ref": "#/components/headers/RateLimit-Remaining" + }, + "RateLimit-Reset": { + "$ref": "#/components/headers/RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawnResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/AuthenticationError" + }, + "403": { + "$ref": "#/components/responses/PermissionError" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + } + } + } + }, + "webhooks": {} +} diff --git a/pyproject.toml b/pyproject.toml index 752e3c5..f7ce30e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,12 @@ build-backend = "hatchling.build" [project] name = "arcmira" dynamic = ["version"] -description = "Arcmira is an SF-based AI company and the search engine for the spoken web." +description = "The Arcmira Python SDK for YouTube and podcast transcripts." readme = "README.md" license = { text = "UNLICENSED" } authors = [{ name = "Arcmira", email = "zeal@arcmira.com" }] requires-python = ">=3.9" +dependencies = ["httpx>=0.21.2,<1", "pydantic>=1.9.2,<3", "typing_extensions>=4.0.0"] keywords = [ "arcmira", "ai", @@ -35,7 +36,7 @@ Repository = "https://github.com/arcmira/python" Issues = "https://github.com/arcmira/python/issues" [tool.hatch.version] -path = "src/arcmira/__init__.py" +path = "src/arcmira/_package.py" [tool.hatch.build.targets.sdist] include = [ diff --git a/reference.md b/reference.md new file mode 100644 index 0000000..0ce559d --- /dev/null +++ b/reference.md @@ -0,0 +1,9563 @@ +# Reference +
client.search(...) -> SearchResolveResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.search( + q="q", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. + +
+
+ +
+
+ +**type:** `typing.Optional[SearchRequestType]` — Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Health +
client.health.check() -> HealthResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.health.check() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Meta +
client.meta.get_openapi_document() -> OpenApiDocument +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.meta.get_openapi_document() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.meta.create_signup(...) -> SignupSentResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.meta.create_signup( + email="email", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**email:** `str` — The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. + +
+
+ +
+
+ +**src:** `typing.Optional[str]` — The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.meta.verify_signup(...) -> SignupVerifiedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.meta.verify_signup( + email="email", + code="code", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**email:** `str` — The address the code was sent to. + +
+
+ +
+
+ +**code:** `str` — The six digit code from the email. Ten minutes, five attempts, then a new send is required. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Me +
client.me.get() -> MeResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Available even when the account has exhausted its usage allowance. Returns the credential making the request (key_id, key_label, credential_kind), the masked account email, the tier, scopes, rate limit, row usage with period_resets_at, and account settings. settings.transcripts is what a transcript request that names no parameter of its own receives: every key of the account resolves against it. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.me.get() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.me.update_settings(...) -> MeSettingsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Sets the account defaults every key of the account resolves against. Send only the fields you are changing; an omitted field keeps the value it has. Resolution order for every transcript request is the explicit parameter, then these settings, then the platform default, so a default never overrides a parameter the caller sent. Defaults live on the account, never on a key: two keys of one account answer the same request the same way. The response echoes the resolved settings. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment +from arcmira.me import UpdateSettingsMeRequestTranscripts + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.me.update_settings( + transcripts=UpdateSettingsMeRequestTranscripts(), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcripts:** `UpdateSettingsMeRequestTranscripts` — Fields to change. An omitted field keeps the value the account already carries. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Entities +
client.entities.search(...) -> EntitySearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Substring name search returning up to 25 entities ordered by appearance count, each with a suggested flag: true on an exact match with far more traction than any other row of its type. A q that is a YouTube channel id (UC...), an @handle, or a YouTube URL carrying either resolves to that one channel row, suggested, with its youtube_channel_id. Callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary per result when a brand profile exists. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.search( + q="q", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` + +
+
+ +
+
+ +**type:** `typing.Optional[SearchEntitiesRequestType]` + +
+
+ +
+
+ +**has_recommendations_data:** `typing.Optional[bool]` + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**src:** `typing.Optional[SearchEntitiesRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.entities.resolve(...) -> EntityResolveResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Call this before passing an id to about, by, entity_ids, channel_ids or channel; those filters refuse names with id_required. Pass context with the user's own words about the name ("the startup bank", "on My First Million"). The answer is one of three: best (the name means one row: use it and name it), suggested (no row is certain but one stands out, with reason and evidence: use it and tell the user you assumed it), or ask (several rows fit: show ask.options, or check every option id and answer per row). For a show pass type=channel and use the youtube_channel_id; for a brand or a person use the id. Free; bills no rows. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.resolve( + q="q", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — A name, @handle, YouTube URL or channel id (UC...). One thing per call. + +
+
+ +
+
+ +**type:** `typing.Optional[ResolveEntitiesRequestType]` — Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Candidates to return, 1 to 15. Default 8. + +
+
+ +
+
+ +**context:** `typing.Optional[str]` — What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. + +
+
+ +
+
+ +**src:** `typing.Optional[ResolveEntitiesRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.entities.lookup(...) -> EntityLookupResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Resolves an id or name to the canonical entity record, following merge redirects. Pass either id (ent_{n} or numeric) or name, optionally constrained by type. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.lookup() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `typing.Optional[str]` + +
+
+ +
+
+ +**name:** `typing.Optional[str]` + +
+
+ +
+
+ +**type:** `typing.Optional[LookupEntitiesRequestType]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.entities.cards(...) -> EntityCardsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Free endpoint (0 rows). Batched compact lookups for UI hover cards: name, slug, type, image, and index counts for up to 50 raw integer entity ids per request. Merged ids resolve to their canonical entity but are returned under the requested id; unknown ids are silently dropped. The response is identical for all viewers and CDN-cacheable. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.cards( + ids="ids", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**ids:** `str` — Comma-separated raw integer entity ids, 1 to 50 of them (e.g. "12,844,1032"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.entities.get(...) -> EntityDetailResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the canonical entity envelope for an ent_{n} or numeric id, following merge redirects. For organization and product entities, callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary commercial-intelligence rollup when a brand profile exists. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.get( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.entities.momentum(...) -> EntityMomentumResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.momentum( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + +
+
+ +
+
+ +**src:** `typing.Optional[MomentumEntitiesRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Mentions +
client.mentions.list(...) -> MentionListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.mentions.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + +
+
+ +
+
+ +**entity_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**entity_name:** `typing.Optional[str]` + +
+
+ +
+
+ +**entity_type:** `typing.Optional[ListMentionsRequestEntityType]` + +
+
+ +
+
+ +**channel_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**channel_name:** `typing.Optional[str]` + +
+
+ +
+
+ +**q:** `typing.Optional[str]` + +
+
+ +
+
+ +**sentiment:** `typing.Optional[ListMentionsRequestSentiment]` + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[bool]` + +
+
+ +
+
+ +**date_from:** `typing.Optional[str]` + +
+
+ +
+
+ +**date_to:** `typing.Optional[str]` + +
+
+ +
+
+ +**details:** `typing.Optional[ListMentionsRequestDetails]` + +
+
+ +
+
+ +**src:** `typing.Optional[ListMentionsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.mentions.count(...) -> MentionCountsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +A small ranked table of entity and channel counts, all-time unless published_after is set. Pass channel_ids for what shows talk about and entity_types to match the question (topic for subjects, person for guests, organization,product for brands). Pass video_ids with one id from GET /v1/channels/{channel_id}/videos for what a single episode mentions. Two or more channel_ids also return shared, the entities on more than one of them ranked by the smallest per-channel count, which is true overlap. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per table row returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.mentions.count() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**channel_ids:** `typing.Optional[str]` — Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them. + +
+
+ +
+
+ +**entity_ids:** `typing.Optional[str]` — Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities. + +
+
+ +
+
+ +**video_ids:** `typing.Optional[str]` — Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos. + +
+
+ +
+
+ +**entity_types:** `typing.Optional[str]` — Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate. + +
+
+ +
+
+ +**mode:** `typing.Optional[CountMentionsRequestMode]` — mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions. + +
+
+ +
+
+ +**published_after:** `typing.Optional[str]` — ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + +
+
+ +
+
+ +**published_before:** `typing.Optional[str]` — ISO date. Only media published before this day. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Rows in the ranked table, 1 to 40. Default 20. + +
+
+ +
+
+ +**src:** `typing.Optional[CountMentionsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Recommendations +
client.recommendations.list(...) -> RecommendationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.recommendations.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + +
+
+ +
+
+ +**entity_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**entity_name:** `typing.Optional[str]` + +
+
+ +
+
+ +**entity_type:** `typing.Optional[ListRecommendationsRequestEntityType]` + +
+
+ +
+
+ +**channel_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**channel_name:** `typing.Optional[str]` + +
+
+ +
+
+ +**mention_class:** `typing.Optional[ListRecommendationsRequestMentionClass]` + +
+
+ +
+
+ +**min_confidence:** `typing.Optional[float]` + +
+
+ +
+
+ +**date_from:** `typing.Optional[str]` + +
+
+ +
+
+ +**date_to:** `typing.Optional[str]` + +
+
+ +
+
+ +**include_disputed:** `typing.Optional[bool]` + +
+
+ +
+
+ +**src:** `typing.Optional[ListRecommendationsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Feedback +
client.feedback.submit(...) -> FeedbackResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alert rows: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert row id, and every referenced alert row must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with no row to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.feedback.submit( + type="recommendations", + query={ + "key": "value" + }, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**type:** `SubmitFeedbackRequestType` — The surface being reviewed. Values: recommendations (/v1/recommendations rows by com_* id; requires a Pro+ plan), channel_sponsors (sponsor entities on a channel; requires a Pro+ plan), mentions (/v1/mentions rows by men_* id), entities_search (/v1/entities/search hits), entities (/v1/entities/lookup and /v1/entities/{id} payloads), channels (/v1/channels/{slug} payloads), monitor_alert (fired alert rows from /v1/monitors/{id}/alerts or /v1/trackers/{id}/alerts; corrections target the alert row id), appearances (person appearance rows), search (rows from /v1/search or /v1/entities/search). + +
+
+ +
+
+ +**query:** `typing.Dict[str, typing.Any]` — The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + +
+
+ +
+
+ +**endpoint:** `typing.Optional[str]` + +
+
+ +
+
+ +**method:** `typing.Optional[SubmitFeedbackRequestMethod]` + +
+
+ +
+
+ +**request_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**result_url:** `typing.Optional[str]` + +
+
+ +
+
+ +**source_url:** `typing.Optional[str]` + +
+
+ +
+
+ +**notes:** `typing.Optional[str]` + +
+
+ +
+
+ +**corrections:** `typing.Optional[typing.List[SubmitFeedbackRequestCorrectionsItem]]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.feedback.get(...) -> FeedbackReadbackResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the submission (id, type, query, notes, created_at) plus its per-correction rows, each with a review status in the public vocabulary: pending_review, needs_information, accepted, accepted_with_changes, rejected, withdrawn, applied, reverted (accepted means a reviewer agreed; applied means the change is live in the index). Only the submitting user's keys can read a submission; unknown ids and other users' submissions both return 404 (never 403). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.feedback.get( + feedback_id="feedback_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**feedback_id:** `str` — The feedback submission id POST /v1/feedback returned. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Transcripts +
client.transcripts.search(...) -> TranscriptSearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to mention, recommendation_sponsored or recommendation_organic passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per chunk returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.search( + q="q", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**q:** `str` — One topic or phrase. Do not concatenate unrelated names; make one call per topic. + +
+
+ +
+
+ +**channel_ids:** `typing.Optional[str]` — Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + +
+
+ +
+
+ +**channel:** `typing.Optional[str]` — Alias of channel_ids for code-mode clients; the union of both is the scope. + +
+
+ +
+
+ +**entity_ids:** `typing.Optional[str]` — Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + +
+
+ +
+
+ +**about:** `typing.Optional[str]` — Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + +
+
+ +
+
+ +**by:** `typing.Optional[str]` — Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + +
+
+ +
+
+ +**kind:** `typing.Optional[str]` — Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk. + +
+
+ +
+
+ +**published_after:** `typing.Optional[str]` — ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + +
+
+ +
+
+ +**published_before:** `typing.Optional[str]` — ISO date. Only media published before this day. + +
+
+ +
+
+ +**source:** `typing.Optional[SearchTranscriptsRequestSource]` — Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Chunks to return, 1 to 20. Default 5. + +
+
+ +
+
+ +**src:** `typing.Optional[SearchTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.get(...) -> TranscriptResult +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.get( + video_id="video_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**quality:** `typing.Optional[GetTranscriptsRequestQuality]` — captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + +
+
+ +
+
+ +**language:** `typing.Optional[str]` — Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + +
+
+ +
+
+ +**timestamps:** `typing.Optional[bool]` — false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. + +
+
+ +
+
+ +**start:** `typing.Optional[float]` — Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge. + +
+
+ +
+
+ +**end:** `typing.Optional[float]` — Window end in seconds, greater than start and no greater than the video duration. Send start and end together. + +
+
+ +
+
+ +**refresh:** `typing.Optional[bool]` — Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. + +
+
+ +
+
+ +**src:** `typing.Optional[GetTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.quote(...) -> TranscriptPurchaseQuote +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.quote( + video_id="video_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.captions(...) -> VideoCaptionsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.captions( + video_id="video_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**src:** `typing.Optional[CaptionsTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.list_requests(...) -> TranscriptionListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.list_requests() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `typing.Optional[str]` — Filter to your requests for one video. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Requests per page, from 1 to 100. Default 20. + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Keep the same filter, limit and credential. + +
+
+ +
+
+ +**src:** `typing.Optional[ListRequestsTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.request(...) -> TranscriptionSubmitResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.request( + idempotency_key="Idempotency-Key", + max_rows=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**idempotency_key:** `str` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + +
+
+ +
+
+ +**max_rows:** `int` — Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. + +
+
+ +
+
+ +**max_on_demand_cents:** `typing.Optional[float]` — Maximum new monetary on-demand charge in cents. Omit to authorize none. + +
+
+ +
+
+ +**video_id:** `typing.Optional[str]` — YouTube video id (11 characters). Either videoId or url is required. + +
+
+ +
+
+ +**url:** `typing.Optional[str]` — A YouTube watch/short/live URL. Either videoId or url is required. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.status(...) -> TranscriptionRequest +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.status( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Transcription request id, the UUID POST /v1/transcriptions returned. + +
+
+ +
+
+ +**src:** `typing.Optional[StatusTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Channels +
client.channels.coverage(...) -> ChannelCoverageResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.coverage( + channel_id="channel_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**channel_id:** `str` — YouTube channel id, the UC... form. + +
+
+ +
+
+ +**src:** `typing.Optional[CoverageChannelsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.channels.get(...) -> ChannelPageResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Channel pages include a recommendations_summary teaser: sponsor_count for all callers; top_sponsors additionally requires a Pro+ plan. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.get( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## People +
client.people.get(...) -> PersonPageResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.get( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Topics +
client.topics.get(...) -> TopicPageResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.topics.get( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Organizations +
client.organizations.get(...) -> OrganizationPageResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.organizations.get( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Products +
client.products.get(...) -> ProductPageResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.products.get( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Monitors +
client.monitors.list() -> MonitorListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +All monitors for the account with tracker counts, alert counts for the current calendar month, and Slack display metadata. Single page, no pagination. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.monitors.create(...) -> MonitorMutationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creating with notifyWebhook: true and a webhookUrl enables HMAC-signed webhook delivery and returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. All subsequent reads expose only webhookSecretSet and webhookSecretHint. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.create( + name="name", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `str` — Display name (1-100 characters). Required on create. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**notify_emails:** `typing.Optional[typing.List[str]]` — Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + +
+
+ +
+
+ +**notify_frequency:** `typing.Optional[CreateMonitorsRequestNotifyFrequency]` — Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + +
+
+ +
+
+ +**digest_day:** `typing.Optional[str]` — Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + +
+
+ +
+
+ +**digest_time:** `typing.Optional[str]` — Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + +
+
+ +
+
+ +**notify_webhook:** `typing.Optional[bool]` — Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + +
+
+ +
+
+ +**webhook_url:** `typing.Optional[str]` — Destination URL for webhook alert deliveries. + +
+
+ +
+
+ +**notify_slack:** `typing.Optional[bool]` — Enable Slack delivery. Requires a Slack integration connected in the dashboard. + +
+
+ +
+
+ +**slack_integration_id:** `typing.Optional[str]` — Slack integration id from the dashboard OAuth flow. + +
+
+ +
+
+ +**slack_channel_id:** `typing.Optional[str]` — Slack channel id to deliver to. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.monitors.delete(...) -> MonitorDeleteResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the monitor AND every tracker inside it (trackersDeleted reports how many). Cannot be undone. Retrying with the original Idempotency-Key returns the original deleted count without deleting again. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.delete( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Monitor id. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.monitors.update(...) -> MonitorMutationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +A PATCH that newly enables webhook signing (turns notifyWebhook on, or sets a webhookUrl where no secret existed before) returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Unrelated PATCHes expose only webhookSecretSet and webhookSecretHint. PATCHing notifyWebhook: true also re-enables a webhook that was auto-disabled after repeated failures and resets its failure counter. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.update( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Monitor id. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**name:** `typing.Optional[str]` — Display name (1-100 characters). Required on create. + +
+
+ +
+
+ +**notify_emails:** `typing.Optional[typing.List[str]]` — Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + +
+
+ +
+
+ +**notify_frequency:** `typing.Optional[UpdateMonitorsRequestNotifyFrequency]` — Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + +
+
+ +
+
+ +**digest_day:** `typing.Optional[str]` — Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + +
+
+ +
+
+ +**digest_time:** `typing.Optional[str]` — Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + +
+
+ +
+
+ +**notify_webhook:** `typing.Optional[bool]` — Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + +
+
+ +
+
+ +**webhook_url:** `typing.Optional[str]` — Destination URL for webhook alert deliveries. + +
+
+ +
+
+ +**notify_slack:** `typing.Optional[bool]` — Enable Slack delivery. Requires a Slack integration connected in the dashboard. + +
+
+ +
+
+ +**slack_integration_id:** `typing.Optional[str]` — Slack integration id from the dashboard OAuth flow. + +
+
+ +
+
+ +**slack_channel_id:** `typing.Optional[str]` — Slack channel id to deliver to. + +
+
+ +
+
+ +**is_paused:** `typing.Optional[bool]` — Paused monitors accept config changes but do not deliver; alerts that would have fired are not queued. + +
+
+ +
+
+ +**is_collapsed:** `typing.Optional[bool]` — Dashboard display state. + +
+
+ +
+
+ +**sort_order:** `typing.Optional[int]` — Dashboard sort position. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.monitors.rotate_webhook_secret(...) -> WebhookSecretRotateResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Generates a new signing secret and returns it in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Zero-downtime overlap: the previous secret remains valid until previousSecretExpiresAt (24 hours); during the window every delivery carries an additional X-Arcmira-Signature-Previous header computed with the old secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. After the window the old secret is dropped and the extra header disappears. Rotating again during the window replaces the previous secret and resets the window. Requires a configured webhook (webhookUrl set); otherwise 409 with code webhook_not_configured. Auto-disable interplay: rotation resets webhook_failures but never re-enables a webhook that was auto-disabled after repeated failures; to resume delivery, also PATCH the monitor with notifyWebhook: true. Requires the monitors:write scope. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.rotate_webhook_secret( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Monitor id. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Trackers +
client.trackers.list() -> TrackerListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +All trackers for the account, newest first, with per-channel delivery counts for the current billing period. Single page, no pagination. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.trackers.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.trackers.create(...) -> TrackerMutationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a standalone tracker watching one entity (name + type, resolved with the same entity resolution Search uses). Attach it to a monitor afterwards via POST /v1/monitors/{id}/trackers. Creating a duplicate (same entity name + type) returns 409 with the existingId. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.trackers.create( + entity_name="entityName", + entity_type="person", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**entity_name:** `str` — The entity name to resolve and watch. Required on create. Creating a duplicate (same name + type) returns 409 with the existingId. + +
+
+ +
+
+ +**entity_type:** `CreateTrackersRequestEntityType` — Entity type of the tracked entity. Required on create. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**display_name:** `typing.Optional[str]` — Optional label shown in alerts and the dashboard. + +
+
+ +
+
+ +**notify_email:** `typing.Optional[bool]` — Per-tracker email delivery. Default true. + +
+
+ +
+
+ +**notify_webhook:** `typing.Optional[bool]` — Per-tracker webhook delivery override. Paid plans only. + +
+
+ +
+
+ +**notify_slack:** `typing.Optional[bool]` — Per-tracker Slack delivery override. Paid plans only. + +
+
+ +
+
+ +**webhook_url:** `typing.Optional[str]` — Per-tracker webhook destination override (http/https). + +
+
+ +
+
+ +**slack_channel_id:** `typing.Optional[str]` — Per-tracker Slack channel override. + +
+
+ +
+
+ +**slack_integration_id:** `typing.Optional[str]` — Per-tracker Slack integration override. + +
+
+ +
+
+ +**person_match_mode:** `typing.Optional[CreateTrackersRequestPersonMatchMode]` — Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + +
+
+ +
+
+ +**filters:** `typing.Optional[typing.Dict[str, typing.Any]]` — Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.trackers.delete(...) -> MessageResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the tracker. Cannot be undone. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.trackers.delete( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Tracker id, trk_ form. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.trackers.update(...) -> TrackerMutationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Partial update: send only the fields to change. The tracked entity itself (entityName/entityType) is immutable; delete and recreate to watch a different entity. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.trackers.update( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Tracker id, trk_ form. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**display_name:** `typing.Optional[str]` — Optional label shown in alerts and the dashboard. + +
+
+ +
+
+ +**notify_email:** `typing.Optional[bool]` — Per-tracker email delivery. Default true. + +
+
+ +
+
+ +**notify_webhook:** `typing.Optional[bool]` — Per-tracker webhook delivery override. Paid plans only. + +
+
+ +
+
+ +**notify_slack:** `typing.Optional[bool]` — Per-tracker Slack delivery override. Paid plans only. + +
+
+ +
+
+ +**webhook_url:** `typing.Optional[str]` — Per-tracker webhook destination override (http/https). + +
+
+ +
+
+ +**slack_channel_id:** `typing.Optional[str]` — Per-tracker Slack channel override. + +
+
+ +
+
+ +**slack_integration_id:** `typing.Optional[str]` — Per-tracker Slack integration override. + +
+
+ +
+
+ +**person_match_mode:** `typing.Optional[UpdateTrackersRequestPersonMatchMode]` — Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + +
+
+ +
+
+ +**filters:** `typing.Optional[typing.Dict[str, typing.Any]]` — Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + +
+
+ +
+
+ +**paused:** `typing.Optional[bool]` — Pause or resume the tracker. Paused trackers stop producing alerts; there is no backfill for the paused window. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Team +
client.team.members() -> TeamMembersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Active members of the team the key is scoped to, with role and seat type, earliest join first. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.team.members() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.team.spend() -> TeamSpendResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Account-wide rows consumed and on-demand overage spend for every active member in the current period. Totals include personal-key use and activity before joining; they are not a team-attributed invoice. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.team.spend() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Corrections +
client.corrections.submit(...) -> CorrectionAcceptedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.corrections.submit( + video_id="video_id", + kind="line_edit", + payload={ + "key": "value" + }, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**kind:** `SubmitCorrectionsRequestKind` + +
+
+ +
+
+ +**payload:** `typing.Dict[str, typing.Any]` — Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + +
+
+ +
+
+ +**seq:** `typing.Optional[int]` — Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. + +
+
+ +
+
+ +**revision:** `typing.Optional[str]` — The revision from the Premium transcript read this correction was made against. Required for every kind. Speaker ids in the payload are the speakers[].id values of that read. + +
+
+ +
+
+ +**anchor:** `typing.Optional[SubmitCorrectionsRequestAnchor]` — Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.corrections.withdraw_speaker_edit(...) -> WithdrawnResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.corrections.withdraw_speaker_edit( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The pending row id, as result.id of the response that accepted it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.corrections.withdraw_entity_tag(...) -> WithdrawnResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.corrections.withdraw_entity_tag( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The pending row id, as result.id of the response that accepted it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.corrections.withdraw_segment_rewrite(...) -> WithdrawnResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.corrections.withdraw_segment_rewrite( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The pending row id, as result.id of the response that accepted it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Channels Sponsors +
client.channels.sponsors.list(...) -> ChannelSponsorsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Rollup of recurring sponsors for a YouTube channel, ordered by ad read count. On a Pro+ plan the full list is served and min_ad_reads (default 3), status, and limit apply. Every other plan receives the free slice an anonymous visitor sees on arcmira.com, with meta.total naming the true count and access naming the gate; passing min_ad_reads, status, or limit on such a plan is refused with filter_requires_paid. Pass src=mcp-tool only from the Arcmira MCP server. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.sponsors.list( + channel_id="channel_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**channel_id:** `str` — YouTube channel id, the UC... form. + +
+
+ +
+
+ +**min_ad_reads:** `typing.Optional[int]` — Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid. + +
+
+ +
+
+ +**status:** `typing.Optional[ListSponsorsRequestStatus]` — Filter against the curated known-advertisers dataset. Pro+ only. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. + +
+
+ +
+
+ +**src:** `typing.Optional[ListSponsorsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Channels Videos +
client.channels.videos.list(...) -> ChannelVideosResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.videos.list( + channel_id="channel_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**channel_id:** `str` — YouTube channel id, the UC... form. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + +
+
+ +
+
+ +**published_after:** `typing.Optional[str]` — ISO date. Only videos published on or after this day. + +
+
+ +
+
+ +**published_before:** `typing.Optional[str]` — ISO date. Only videos published before this day. + +
+
+ +
+
+ +**src:** `typing.Optional[ListVideosRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Channels Related +
client.channels.related.topics(...) -> EntityTopicListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.related.topics( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[TopicsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[TopicsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[TopicsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.channels.related.people(...) -> EntityPeopleListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.related.people( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[PeopleRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[PeopleRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[PeopleRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.channels.related.organizations(...) -> EntityOrganizationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.related.organizations( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[OrganizationsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[OrganizationsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[OrganizationsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.channels.related.products(...) -> EntityProductListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.related.products( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ProductsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ProductsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ProductsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.channels.related.channels(...) -> EntityChannelListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.related.channels( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ChannelsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ChannelsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ChannelsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Channels Guests +
client.channels.guests.list(...) -> ChannelGuestListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.channels.guests.list( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ListGuestsRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ListGuestsRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ListGuestsRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Entities Mentions +
client.entities.mentions.list(...) -> MentionListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cursor-paginated mentions for one entity, newest media first. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.mentions.list( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + +
+
+ +
+
+ +**channel_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**channel_name:** `typing.Optional[str]` + +
+
+ +
+
+ +**q:** `typing.Optional[str]` + +
+
+ +
+
+ +**sentiment:** `typing.Optional[ListMentionsRequestSentiment]` + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[bool]` + +
+
+ +
+
+ +**date_from:** `typing.Optional[str]` + +
+
+ +
+
+ +**date_to:** `typing.Optional[str]` + +
+
+ +
+
+ +**details:** `typing.Optional[ListMentionsRequestDetails]` + +
+
+ +
+
+ +**src:** `typing.Optional[ListMentionsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Entities Recommendations +
client.entities.recommendations.list(...) -> RecommendationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.entities.recommendations.list( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + +
+
+ +
+
+ +**channel_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**channel_name:** `typing.Optional[str]` + +
+
+ +
+
+ +**mention_class:** `typing.Optional[ListRecommendationsRequestMentionClass]` + +
+
+ +
+
+ +**min_confidence:** `typing.Optional[float]` + +
+
+ +
+
+ +**date_from:** `typing.Optional[str]` + +
+
+ +
+
+ +**date_to:** `typing.Optional[str]` + +
+
+ +
+
+ +**include_disputed:** `typing.Optional[bool]` + +
+
+ +
+
+ +**src:** `typing.Optional[ListRecommendationsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Monitors Trackers +
client.monitors.trackers.list(...) -> MonitorTrackersResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.trackers.list( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Monitor id. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.monitors.trackers.add(...) -> MonitorAddTrackersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Attaches EXISTING trackers to the monitor by id ({ trackerIds: ["trk_..."] }). It does not create trackers: create them first via POST /v1/trackers, then attach. Attached trackers use the monitor's delivery settings. Supply 1 to 90 IDs. Duplicate IDs count once. Every ID must belong to the account; a missing or foreign ID returns tracker_not_found and none are attached. attachedCount reports the unique attached count. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.trackers.add( + id="id", + tracker_ids=[ + "trackerIds" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Monitor id. + +
+
+ +
+
+ +**tracker_ids:** `typing.List[str]` — Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Monitors Alerts +
client.monitors.alerts.list(...) -> AlertListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.monitors.alerts.list( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Monitor id. + +
+
+ +
+
+ +**n:** `typing.Optional[int]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Organizations Related +
client.organizations.related.topics(...) -> EntityTopicListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.organizations.related.topics( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[TopicsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[TopicsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[TopicsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.organizations.related.people(...) -> EntityPeopleListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.organizations.related.people( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[PeopleRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[PeopleRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[PeopleRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.organizations.related.organizations(...) -> EntityOrganizationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.organizations.related.organizations( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[OrganizationsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[OrganizationsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[OrganizationsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.organizations.related.products(...) -> EntityProductListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.organizations.related.products( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ProductsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ProductsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ProductsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.organizations.related.channels(...) -> EntityChannelListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.organizations.related.channels( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ChannelsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ChannelsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ChannelsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## People Appearances +
client.people.appearances.list(...) -> PersonAppearanceListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.appearances.list( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ListAppearancesRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ListAppearancesRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ListAppearancesRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## People Related +
client.people.related.topics(...) -> EntityTopicListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.related.topics( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[TopicsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[TopicsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[TopicsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.people.related.people(...) -> EntityPeopleListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.related.people( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[PeopleRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[PeopleRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[PeopleRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.people.related.organizations(...) -> EntityOrganizationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.related.organizations( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[OrganizationsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[OrganizationsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[OrganizationsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.people.related.products(...) -> EntityProductListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.related.products( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ProductsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ProductsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ProductsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.people.related.channels(...) -> EntityChannelListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.people.related.channels( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ChannelsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ChannelsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ChannelsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Products Related +
client.products.related.topics(...) -> EntityTopicListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.products.related.topics( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[TopicsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[TopicsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[TopicsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.products.related.people(...) -> EntityPeopleListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.products.related.people( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[PeopleRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[PeopleRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[PeopleRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.products.related.organizations(...) -> EntityOrganizationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.products.related.organizations( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[OrganizationsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[OrganizationsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[OrganizationsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.products.related.products(...) -> EntityProductListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.products.related.products( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ProductsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ProductsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ProductsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.products.related.channels(...) -> EntityChannelListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.products.related.channels( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ChannelsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ChannelsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ChannelsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Team UsageEvents +
client.team.usage_events.list(...) -> TeamUsageEventsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Account usage log across every active team member, including personal-key use and activity before joining. Ordered by created_at and id descending, bounded to a 90-day look-back. Continuation preserves the first page's time window and excludes subsequently inserted events, including backfills. Removed members and deleted events disappear during traversal; this is not a historical membership snapshot. Cursors expire after 24 hours and bind the team, caller, days and limit; invalid or changed-query cursors return invalid_cursor rather than restarting. The aggregated analytics chart data is not exposed on this API (Enterprise). Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.team.usage_events.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Events per page, 1 to 100. + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Opaque cursor from a previous page's next_cursor. + +
+
+ +
+
+ +**days:** `typing.Optional[int]` — Look-back window in days, bounded at 90. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Topics Related +
client.topics.related.topics(...) -> EntityTopicListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.topics.related.topics( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[TopicsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[TopicsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[TopicsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.topics.related.people(...) -> EntityPeopleListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.topics.related.people( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[PeopleRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[PeopleRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[PeopleRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.topics.related.organizations(...) -> EntityOrganizationListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.topics.related.organizations( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[OrganizationsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[OrganizationsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[OrganizationsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.topics.related.products(...) -> EntityProductListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.topics.related.products( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ProductsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ProductsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ProductsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.topics.related.channels(...) -> EntityChannelListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.topics.related.channels( + slug="slug", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**slug:** `str` — The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + +
+
+ +
+
+ +**q:** `typing.Optional[str]` — Substring filter over the row's text columns (e.g. video title, channel name, description). + +
+
+ +
+
+ +**field:** `typing.Optional[str]` — Restrict the q filter to one column. Default "any" (all searchable columns). + +
+
+ +
+
+ +**sort:** `typing.Optional[str]` — Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + +
+
+ +
+
+ +**order:** `typing.Optional[ChannelsRelatedRequestOrder]` — Sort direction. Default desc. + +
+
+ +
+
+ +**mode:** `typing.Optional[ChannelsRelatedRequestMode]` — Person relationship lens: guest appearances or inbound mentions. + +
+
+ +
+
+ +**is_appearance:** `typing.Optional[ChannelsRelatedRequestIsAppearance]` — For person appearances, true lists guest episodes and false lists inbound mentions. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Trackers Alerts +
client.trackers.alerts.list(...) -> AlertListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.trackers.alerts.list( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — Tracker id, trk_ form. + +
+
+ +
+
+ +**n:** `typing.Optional[int]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Transcripts Edits +
client.transcripts.edits.submit(...) -> TranscriptEditSubmittedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Purpose-built wrapper for the line_edit kind. The edit is pending review: visible to you immediately (returned in the transcript GET `edits[]`), applied for everyone once approved. Free (0 rows), attributed to your API key. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.edits.submit( + video_id="video_id", + segment_index=1, + original_text="originalText", + corrected_text="correctedText", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**segment_index:** `int` + +
+
+ +
+
+ +**original_text:** `str` — The current segment text you are correcting (guards against applying to a changed segment). + +
+
+ +
+
+ +**corrected_text:** `str` + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + +
+
+ +
+
+ +**revision:** `typing.Optional[str]` — The revision from the Premium transcript read. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.edits.withdraw(...) -> WithdrawnResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.edits.withdraw( + video_id="video_id", + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**id:** `str` — The pending row id, as result.id of the response that accepted it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Transcripts Speakers +
client.transcripts.speakers.identify(...) -> SpeakerIdentificationSubmittedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Links a diarization speaker id to a person entity (or proposes a new person via `name`). Creates a community-attributed appearance immediately. It shows on the person page right away, flagged pending review; reviewers can revert it. Free (0 rows). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.speakers.identify( + video_id="video_id", + speaker_id=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**speaker_id:** `int` — A speakers[].id from the Premium transcript read that revision names. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + +
+
+ +
+
+ +**entity_id:** `typing.Optional[int]` — Existing person entity id. Either entityId or name is required. + +
+
+ +
+
+ +**name:** `typing.Optional[str]` — Propose a person not in the index yet. Reuses an existing same-name person or creates a provisional one. Either entityId or name is required. + +
+
+ +
+
+ +**revision:** `typing.Optional[str]` — The revision of the transcript read speakerId came from. Required. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.speakers.withdraw(...) -> WithdrawnResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Withdrawing also removes the community-attributed appearance the identification created. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.speakers.withdraw( + video_id="video_id", + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**id:** `str` — The pending row id, as result.id of the response that accepted it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Transcripts Merges +
client.transcripts.merges.list(...) -> VideoMergeListResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.merges.list( + video_id="video_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.merges.submit(...) -> VideoMergeSubmittedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Asserts that a name in this video refers to a specific entity, for misattributed name mentions in one video (e.g. a first-name-only mention resolved to the wrong entity). Optionally respells the transcript text via `replaceWith`. Pending review; applied optimistically for you. Mentions of the same name in other videos are untouched. Free (0 rows). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.merges.submit( + video_id="video_id", + source_name="sourceName", + target_entity_id=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**source_name:** `str` — The name as it appears in this video (e.g. a first-name-only mention). + +
+
+ +
+
+ +**target_entity_id:** `int` — The canonical entity these mentions actually refer to. + +
+
+ +
+
+ +**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + +
+
+ +
+
+ +**replace_with:** `typing.Optional[str]` — Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). + +
+
+ +
+
+ +**revision:** `typing.Optional[str]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.transcripts.merges.withdraw(...) -> WithdrawnResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from arcmira import Arcmira +from arcmira.environment import ArcmiraEnvironment + +client = Arcmira( + api_key="", + environment=ArcmiraEnvironment.DEFAULT, +) + +client.transcripts.merges.withdraw( + video_id="video_id", + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**video_id:** `str` — YouTube video id, 11 characters. + +
+
+ +
+
+ +**id:** `str` — The pending row id, as result.id of the response that accepted it. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
diff --git a/scripts/check-commit-identity.sh b/scripts/check-commit-identity.sh new file mode 100755 index 0000000..e3bd07d --- /dev/null +++ b/scripts/check-commit-identity.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Verify every commit introduced after the existing public release baseline. +# Bootstrap history is preserved without rewriting its earlier author metadata. +set -euo pipefail +cd "$(dirname "$0")/.." +AUTHOR='zealous1@users.noreply.github.com' +GITHUB_MERGE='noreply@github.com' +SIGNERS="$PWD/.github/allowed_signers" +range="${1:-a2dd1113225160b3e870e7c569f16d59abd044ea..HEAD}" +failed=0 +while IFS=$'\t' read -r sha author committer; do + problems=() + [[ "$author" == "$AUTHOR" ]] || problems+=("author $author") + if [[ "$committer" == "$GITHUB_MERGE" ]]; then + : + elif [[ "$committer" != "$AUTHOR" ]]; then + problems+=("committer $committer") + elif ! git -c gpg.format=ssh -c gpg.ssh.allowedSignersFile="$SIGNERS" verify-commit "$sha" >/dev/null 2>&1; then + problems+=("not signed by a key in .github/allowed_signers") + fi + if ((${#problems[@]})); then + printf '%s %s: %s\n' "$(git rev-parse --short "$sha")" "$(git log -1 --format=%s "$sha" | cut -c1-60)" "$(printf '%s; ' "${problems[@]}" | sed 's/; $//')" + failed=1 + fi +done < <(git log --format='%H%x09%ae%x09%ce' "$range") +if ((failed)); then + echo "commit identity check failed: only zealous1 commits, signed with the key in .github/allowed_signers, may exist here" >&2 + exit 1 +fi +echo "commit identity check: $(git rev-list --count "$range") commits, every one zealous1 and signed" diff --git a/scripts/generate.sh b/scripts/generate.sh new file mode 100755 index 0000000..e40efba --- /dev/null +++ b/scripts/generate.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +export FERN_NO_VERSION_REDIRECTION=true +export DO_NOT_TRACK=1 +python3 scripts/prepare-openapi.py +version="$(cat VERSION)" +npm exec --yes --package=fern-api@5.131.1 -- fern generate --local --group python --version "$version" --force --no-prompt +python3 scripts/install-generated.py diff --git a/scripts/install-generated.py b/scripts/install-generated.py new file mode 100644 index 0000000..d75977f --- /dev/null +++ b/scripts/install-generated.py @@ -0,0 +1,32 @@ +"""Copy Fern output into the package, preserving the public URL constants.""" +from pathlib import Path +import shutil +root = Path(__file__).resolve().parents[1] +source = root / '.generated/python' +target = root / 'src/arcmira' +assert (source / 'client.py').exists() +shutil.rmtree(target) +for path in source.rglob('*.py'): + if 'tests' in path.relative_to(source).parts: + continue + dest = target / path.relative_to(source) + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text(path.read_text().rstrip() + '\n') +(target / 'py.typed').touch() +# The previous public pointer package exported these constants. +init = target / '__init__.py' +text = init.read_text() +assert text.startswith('# This file was auto-generated by Fern') +init.write_text(text + '\nfrom ._package import __version__, homepage, docs, api_base, openapi, llms_txt, docs_llms_txt\n') +version = (root / 'VERSION').read_text().strip() +(target / '_package.py').write_text(f'''# Written by scripts/install-generated.py from VERSION. +__version__ = {version!r} +homepage = "https://arcmira.com" +docs = "https://arcmira.com/docs" +api_base = "https://api.arcmira.com/v1" +openapi = "https://api.arcmira.com/v1/openapi.json" +llms_txt = "https://arcmira.com/llms.txt" +docs_llms_txt = "https://arcmira.com/docs/llms.txt" +''') +reference = (source / 'reference.md').read_text() +(root / 'reference.md').write_text('\n'.join(line.rstrip() for line in reference.splitlines()).rstrip() + '\n') diff --git a/scripts/prepare-openapi.py b/scripts/prepare-openapi.py new file mode 100644 index 0000000..0d89692 --- /dev/null +++ b/scripts/prepare-openapi.py @@ -0,0 +1,94 @@ +"""Build Fern's public generation input without changing the HTTP contract.""" +import copy +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def resolve(document, schema): + while '$ref' in schema: + ref = schema['$ref'] + if not ref.startswith('#/'): + raise ValueError(f'External schema reference: {ref}') + schema = document + for part in ref[2:].split('/'): + schema = schema[part.replace('~1', '/').replace('~0', '~')] + return schema + + +def collection(document, schema): + props = resolve(document, schema).get('properties', {}) + arrays = [key for key, value in props.items() if resolve(document, value).get('type') == 'array'] + if len(arrays) != 1 or arrays[0] not in {'data', 'requests', 'episodes', 'items'}: + raise ValueError(f'Unknown or ambiguous cursor collection: {arrays}') + return arrays[0] + + +def prepare(document, names): + doc = copy.deepcopy(document) + # Fern 5.131.1 loses inherited example fields in this object intersection. + suggestion = doc['components']['schemas']['ResolveSuggestion'] + members = [resolve(doc, part) for part in suggestion.pop('allOf')] + suggestion.update(type='object', properties={}, required=[]) + for member in members: + suggestion['properties'].update(member['properties']) + suggestion['required'].extend(member.get('required', [])) + doc['servers'] = [{'url': 'https://api.arcmira.com'}] + doc['components']['securitySchemes']['bearerAuth']['x-fern-bearer'] = {'name': 'apiKey', 'env': 'ARCMIRA_API_KEY'} + special = { + 'quote_transcription': ('transcripts', 'quote'), + 'submit_transcription': ('transcripts', 'request'), + 'get_transcription': ('transcripts', 'status'), + 'list_transcriptions': ('transcripts', 'listRequests'), + } + for path, methods in doc['paths'].items(): + for method, op in methods.items(): + if method not in {'get', 'post', 'put', 'patch', 'delete'}: + continue + key = method + ' ' + re.sub(r'\{[^}]+\}', '{}', path) + if op.get('operationId') in special: + group, name = special[op['operationId']] + op['x-fern-sdk-group-name'] = group + op['x-fern-sdk-method-name'] = name + elif key in names: + op['x-fern-sdk-group-name'] = names[key]['group'] + op['x-fern-sdk-method-name'] = names[key]['method'] + if path == '/v1/feedback' and method == 'post': + # Legacy query alternatives collide with the established body API. + op['parameters'] = [p for p in op.get('parameters', []) if not (p.get('in') == 'query' and p['name'] in {'type', 'query'})] + body = op['requestBody']['content']['application/json']['schema'] + body['required'] = sorted(set(body.get('required', [])) | {'type', 'query'}) + responses = [] + for code, response in op.get('responses', {}).items(): + if code.startswith('2'): + response = resolve(doc, response) + schema = response.get('content', {}).get('application/json', {}).get('schema') + if schema is not None and schema not in responses: + responses.append(schema) + if len(responses) > 1: + states = {} + for schema in responses: + state = resolve(doc, schema).get('properties', {}).get('state', {}).get('enum', []) + if len(state) != 1 or state[0] in states or '$ref' not in schema: + raise ValueError(f'Cannot discriminate success schemas for {method} {path}') + states[state[0]] = schema['$ref'] + union_name = 'TranscriptResult' if op['operationId'] == 'get_transcript' else op['operationId'] + 'Result' + doc['components']['schemas'][union_name] = {'oneOf': responses, 'discriminator': {'propertyName': 'state', 'mapping': states}} + for code, response in op['responses'].items(): + if code.startswith('2'): + response['content']['application/json']['schema'] = {'$ref': '#/components/schemas/' + union_name} + if method == 'get' and any(p.get('name') == 'cursor' and p.get('in') == 'query' for p in op.get('parameters', [])): + if len(responses) != 1: + raise ValueError(f'Cursor operation lacks a single collection response: {path}') + items = collection(doc, responses[0]) + if 'next_cursor' not in resolve(doc, responses[0]).get('properties', {}): + raise ValueError(f'Cursor operation lacks next_cursor: {path}') + op['x-fern-pagination'] = {'cursor': '$request.cursor', 'next_cursor': '$response.next_cursor', 'results': '$response.' + items} + return doc + + +if __name__ == '__main__': + doc = prepare(json.loads((ROOT / 'fern/openapi.json').read_text()), json.loads((ROOT / 'fern/method-names.json').read_text())) + (ROOT / 'fern/openapi.sdk.json').write_text(json.dumps(doc, indent=2) + '\n') diff --git a/src/arcmira/__init__.py b/src/arcmira/__init__.py index b51318d..4030353 100644 --- a/src/arcmira/__init__.py +++ b/src/arcmira/__init__.py @@ -1,19 +1,1460 @@ -"""Official PyPI name for Arcmira. Exports public URLs. Not an SDK.""" +# This file was auto-generated by Fern from our API Definition. -__version__ = "0.1.1" +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + AccountSettings, + Alert, + AlertEvidenceKind, + AlertListResponse, + AlertMonitor, + AlertTracker, + BadRankingChange, + CaptionTrack, + ChannelCoverageResponse, + ChannelCoverageResponseChannel, + ChannelCoverageResponseChannelSourceMix, + ChannelGuestListResponse, + ChannelGuestListResponseExportCapabilities, + ChannelGuestListResponseItemsItem, + ChannelGuestListResponseItemsItemSentiment, + ChannelPageResponse, + ChannelPageResponseChannelInfo, + ChannelPageResponseEntity, + ChannelPageResponseEntityOwner, + ChannelPageResponseEntityType, + ChannelPageResponseEpisodesByMonthItem, + ChannelPageResponseEpisodesItem, + ChannelPageResponseEpisodesItemPlatform, + ChannelPageResponseEpisodesItemSentiment, + ChannelPageResponseEpisodesItemTimestamp, + ChannelPageResponseEpisodesItemType, + ChannelPageResponseGuestsItem, + ChannelPageResponseGuestsItemRole, + ChannelPageResponseGuestsItemSentiment, + ChannelPageResponseHostsDetailedItem, + ChannelPageResponseHostsDetailedItemSentiment, + ChannelPageResponseOrganizationsItem, + ChannelPageResponseOrganizationsItemSentiment, + ChannelPageResponseProductsItem, + ChannelPageResponseProductsItemSentiment, + ChannelPageResponseRecommendationsSummary, + ChannelPageResponseStats, + ChannelPageResponseTopicsItem, + ChannelPageResponseTopicsItemSentiment, + ChannelSponsor, + ChannelSponsorEntity, + ChannelSponsorSponsorStatus, + ChannelSponsorsResponse, + ChannelSponsorsResponseAccess, + ChannelSponsorsResponseAccessGate, + ChannelSponsorsResponseAccessReason, + ChannelSponsorsResponseAccessType, + ChannelSponsorsResponseAccessUnlock, + ChannelSponsorsResponseAccessUnlockAction, + ChannelSponsorsResponseChannel, + ChannelSponsorsResponseMeta, + ChannelVideosResponse, + ChannelVideosResponseChannel, + ChannelVideosResponseEpisodesItem, + CorrectionAcceptedResponse, + CorrectionAcceptedResponseKind, + CorrectionSeqMismatchResponse, + DeliveryIssueChange, + DeliveryIssueChangeChannel, + Entity, + EntityCard, + EntityCardsResponse, + EntityChannelListResponse, + EntityChannelListResponseExportCapabilities, + EntityChannelListResponseItemsItem, + EntityDetailRecommendationsSummary, + EntityDetailResponse, + EntityLookupResponse, + EntityMomentumResponse, + EntityMomentumResponseAccess, + EntityMomentumResponseAccessGate, + EntityMomentumResponseAccessReason, + EntityMomentumResponseAccessType, + EntityMomentumResponseAccessUnlock, + EntityMomentumResponseAccessUnlockAction, + EntityMomentumResponsePaidVsOrganic, + EntityMomentumResponseTopShowsItem, + EntityMomentumResponseVerdict, + EntityMomentumResponseVolume, + EntityOrganizationListResponse, + EntityOrganizationListResponseExportCapabilities, + EntityOrganizationListResponseItemsItem, + EntityOrganizationListResponseItemsItemSentiment, + EntityPageMention, + EntityPageMentionExcerpt, + EntityPageMentionExcerptPublicSourceClass, + EntityPageMentionPlatform, + EntityPageMentionSentiment, + EntityPageMentionType, + EntityPeopleListResponse, + EntityPeopleListResponseExportCapabilities, + EntityPeopleListResponseItemsItem, + EntityPeopleListResponseItemsItemSentiment, + EntityPeopleListResponsePeopleMode, + EntityProductListResponse, + EntityProductListResponseExportCapabilities, + EntityProductListResponseItemsItem, + EntityProductListResponseItemsItemSentiment, + EntityRef, + EntityResolveResponse, + EntityResolveResponseAsk, + EntityResolveResponseAskOptionsItem, + EntityResolveResponseConfidence, + EntitySearchResponse, + EntitySearchResult, + EntitySearchResultRecommendationsSummary, + EntityTopicListResponse, + EntityTopicListResponseExportCapabilities, + EntityTopicListResponseItemsItem, + EntityTopicListResponseItemsItemSentiment, + Error, + ErrorError, + ErrorErrorGate, + ErrorErrorReason, + ErrorErrorType, + ErrorErrorUnlock, + ErrorErrorUnlockAction, + ExposureMeta, + ExposureMetaAccess, + ExposureMetaAccessChart, + ExposureMetaAccessCls, + ExposureMetaAccessFreshness, + ExposureMetaAccessLadder, + ExposureMetaAccessRows, + ExposureMetaAccessRowsEntities, + ExposureMetaAccessRowsMedia, + ExposureMetaAccessRowsTopics, + ExposureMetaAccessUnlock, + ExposureMetaAccessUnlockLimitAction, + ExposureMetaAccessUnlockSrc, + ExposureMetaAccessView, + ExposureMetaAccessWithheldItem, + ExposureMetaAccessWithheldItemKind, + ExposureMetaAccessWithheldItemParam, + ExposureMetaAccessWithheldItemSection, + ExposureMetaAccessWithheldItemWhat, + ExposureMetaCredits, + ExposureMetaCreditsOnDemand, + ExposureMetaCreditsPlan, + ExposureMetaFreeLimit, + ExposureMetaLimitAction, + ExposureMetaLimits, + ExposureMetaRecentPreview, + ExposureMetaRecentPreviewExperiment, + ExposureMetaRecentPreviewMentions, + ExposureMetaRecentPreviewMentionsExperiment, + ExposureMetaRecentPreviewMentionsSubject, + ExposureMetaRecentPreviewMentionsTeaserItemsItem, + ExposureMetaRecentPreviewSubject, + ExposureMetaRecentPreviewTeaserItemsItem, + ExposureMetaTotals, + ExposureMetaUsageLimitType, + FeedbackCorrectionResult, + FeedbackCorrectionResultRecommendation, + FeedbackCorrectionResultRecommendationMedia, + FeedbackCorrectionResultRecommendationMediaSourceChannel, + FeedbackCorrectionResultStatus, + FeedbackReadbackCorrection, + FeedbackReadbackCorrectionStatus, + FeedbackReadbackResponse, + FeedbackReadbackResponseStatus, + FeedbackResponse, + FreeformSuggestedChange, + HealthResponse, + HealthResponseStatus, + HealthResponseVersion, + MeResponse, + MeResponseCredentialKind, + MeResponseUsage, + MeResponseUsageCredits, + MeResponseUsageCreditsOnDemand, + MeResponseUsageCreditsPlan, + MeResponseUsageHits, + MeSettingsResponse, + Mention, + MentionCountsResponse, + MentionCountsResponseMode, + MentionCountsResponseRowsItem, + MentionCountsResponseSharedItem, + MentionCountsResponseSharedItemByChannelItem, + MentionListResponse, + MentionListResponseEntity, + MentionListResponseUnlock, + MentionMedia, + MentionMediaSourceChannel, + MentionRecommendations, + MentionSentiment, + MergeSuggestionChange, + MessageResponse, + MissedAlertChange, + MissingResultChange, + Monitor, + MonitorAddTrackersResponse, + MonitorDeleteResponse, + MonitorEmailRecipientsItem, + MonitorEmailRecipientsItemInvitationStatus, + MonitorEmailRecipientsItemStatus, + MonitorListResponse, + MonitorListResponseMonitorsItem, + MonitorListResponseMonitorsItemSlackIntegration, + MonitorMutationResponse, + MonitorMutationResponseMonitor, + MonitorTrackersResponse, + MonitorTrackersResponseTrackersItem, + NamedEntityRef, + OpenApiDocument, + OpenApiDocumentInfo, + OpenApiDocumentInfoContact, + OpenApiDocumentServersItem, + OrganizationPageResponse, + OrganizationPageResponseChannelsItem, + OrganizationPageResponseChannelsItemSentiment, + OrganizationPageResponseEntity, + OrganizationPageResponseEntityOwnedChannelsItem, + OrganizationPageResponseEntityOwnedProductsItem, + OrganizationPageResponseEntityType, + OrganizationPageResponseMentionsByMonthItem, + OrganizationPageResponsePeopleItem, + OrganizationPageResponsePeopleItemSentiment, + OrganizationPageResponseProductsItem, + OrganizationPageResponseProductsItemSentiment, + OrganizationPageResponseRoleEdge, + OrganizationPageResponseRoleEdgeLabel, + OrganizationPageResponseRoleEdgePeopleItem, + OrganizationPageResponseRoleEdgeRecentAppearancesItem, + OrganizationPageResponseRoleEdgeRole, + OrganizationPageResponseStats, + OrganizationPageResponseTopicsItem, + OrganizationPageResponseTopicsItemSentiment, + PersonAppearanceListResponse, + PersonAppearanceListResponseItemsItem, + PersonAppearanceListResponseItemsItemPlatform, + PersonAppearanceListResponseItemsItemSentiment, + PersonAppearanceListResponseItemsItemType, + PersonPageResponse, + PersonPageResponseAppearancesByMonthItem, + PersonPageResponseAppearancesItem, + PersonPageResponseAppearancesItemPlatform, + PersonPageResponseAppearancesItemSentiment, + PersonPageResponseAppearancesItemType, + PersonPageResponseBrandsItem, + PersonPageResponseBrandsItemSentiment, + PersonPageResponseEntity, + PersonPageResponseEntityOwnedChannelsItem, + PersonPageResponseEntityOwnedProductsItem, + PersonPageResponseMentionsByMonthItem, + PersonPageResponseMentionsItem, + PersonPageResponseMentionsItemPlatform, + PersonPageResponseMentionsItemSentiment, + PersonPageResponseMentionsItemType, + PersonPageResponsePeopleItem, + PersonPageResponsePeopleItemRole, + PersonPageResponsePeopleItemSentiment, + PersonPageResponseProductsItem, + PersonPageResponseProductsItemSentiment, + PersonPageResponseRoleEdge, + PersonPageResponseRoleEdgeLabel, + PersonPageResponseRoleEdgeRole, + PersonPageResponseStats, + PersonPageResponseTopicsItem, + PersonPageResponseTopicsItemSentiment, + ProductPageResponse, + ProductPageResponseChannelsItem, + ProductPageResponseChannelsItemSentiment, + ProductPageResponseEntity, + ProductPageResponseEntityOwner, + ProductPageResponseEntityParentOrg, + ProductPageResponseEntityType, + ProductPageResponseMentionsByMonthItem, + ProductPageResponseOpportunities, + ProductPageResponseOrganizationsItem, + ProductPageResponseOrganizationsItemSentiment, + ProductPageResponsePeopleItem, + ProductPageResponsePeopleItemSentiment, + ProductPageResponseStats, + ProductPageResponseTopicsItem, + ProductPageResponseTopicsItemSentiment, + PublishedExcerpt, + PublishedExcerptPublicSourceClass, + Recommendation, + RecommendationEnrichmentItem, + RecommendationListResponse, + RecommendationListResponseEntity, + RecommendationMedia, + RecommendationMediaSourceChannel, + ResolveCandidate, + ResolveCandidateMatch, + ResolveSuggestion, + ResolveSuggestionMatch, + ResolveSuggestionReason, + SearchRequestType, + SearchResolveResponse, + SearchResolveResponseEntity, + SignupSentResponse, + SignupSentResponseNext, + SignupSentResponseNextMethod, + SignupVerifiedResponse, + SpeakerIdentificationSubmittedResponse, + SpeakerIdentificationSubmittedResponseIdentification, + SpeakerIdentificationSubmittedResponseIdentificationEntity, + SpeakerIdentificationSubmittedResponseIdentificationStatus, + StaleMetadataChange, + TeamMember, + TeamMemberRole, + TeamMemberSeatType, + TeamMemberSpend, + TeamMemberSpendRole, + TeamMemberSpendSeatType, + TeamMembersResponse, + TeamMembersResponseTeam, + TeamSpendResponse, + TeamUsageEvent, + TeamUsageEventsResponse, + TopicPageResponse, + TopicPageResponseChannelsItem, + TopicPageResponseChannelsItemSentiment, + TopicPageResponseCompaniesItem, + TopicPageResponseCompaniesItemSentiment, + TopicPageResponseEntity, + TopicPageResponseEntityType, + TopicPageResponseMentionsByMonthItem, + TopicPageResponseProductsItem, + TopicPageResponseProductsItemSentiment, + TopicPageResponseRelatedTopicsItem, + TopicPageResponseRelatedTopicsItemSentiment, + TopicPageResponseStats, + TopicPageResponseVoicesItem, + TopicPageResponseVoicesItemRole, + TopicPageResponseVoicesItemSentiment, + Tracker, + TrackerListResponse, + TrackerMutationResponse, + TranscriptEditSubmittedResponse, + TranscriptEditSubmittedResponseEdit, + TranscriptEditSubmittedResponseEditStatus, + TranscriptPending, + TranscriptPendingPremiumJob, + TranscriptPendingQuality, + TranscriptPurchaseQuote, + TranscriptPurchaseQuoteBillingScope, + TranscriptPurchaseQuoteCharge, + TranscriptPurchaseQuoteChargeUnit, + TranscriptQuote, + TranscriptResponse, + TranscriptResponseAccess, + TranscriptResponseAccessGate, + TranscriptResponseAccessReason, + TranscriptResponseAccessType, + TranscriptResponseAccessUnlock, + TranscriptResponseAccessUnlockAction, + TranscriptResponseLinesItem, + TranscriptResponseParagraphsItem, + TranscriptResponsePremiumJob, + TranscriptResponseQuality, + TranscriptResponseRange, + TranscriptResponseSource, + TranscriptResponseSpeakersItem, + TranscriptResult, + TranscriptResult_Pending, + TranscriptResult_Ready, + TranscriptSearchChunk, + TranscriptSearchResponse, + TranscriptSearchResponseAccess, + TranscriptSearchResponseAccessGate, + TranscriptSearchResponseAccessReason, + TranscriptSearchResponseAccessType, + TranscriptSearchResponseAccessUnlock, + TranscriptSearchResponseAccessUnlockAction, + TranscriptSearchResponseFilters, + TranscriptSearchResponseSearchIndex, + TranscriptSearchResponseSearchIndexState, + TranscriptSettings, + TranscriptSettingsQuality, + TranscriptVideo, + TranscriptionListResponse, + TranscriptionListResponseRequestsItem, + TranscriptionRequest, + TranscriptionRequestCharge, + TranscriptionRequestChargeUnit, + TranscriptionRequestQuote, + TranscriptionRequestStage, + TranscriptionRequestState, + TranscriptionRequestStatus, + TranscriptionSubmitResponse, + VideoCaptionsResponse, + VideoMergeListResponse, + VideoMergeListResponseMergesItem, + VideoMergeListResponseMergesItemStatus, + VideoMergeSubmittedResponse, + VideoMergeSubmittedResponseMerge, + VideoMergeSubmittedResponseMergeStatus, + WebhookSecretRotateResponse, + WithdrawnResponse, + WrongClassificationChange, + WrongClassificationChangeMentionClass, + WrongEntityChange, + WrongEntityTypeChange, + WrongEntityTypeChangeField, + ) + from .errors import ( + BadRequestError, + ConflictError, + ForbiddenError, + InternalServerError, + NotFoundError, + PaymentRequiredError, + PreconditionFailedError, + ServiceUnavailableError, + TooManyRequestsError, + UnauthorizedError, + UnprocessableEntityError, + ) + from . import ( + channels, + corrections, + entities, + feedback, + health, + me, + mentions, + meta, + monitors, + organizations, + people, + products, + recommendations, + team, + topics, + trackers, + transcripts, + ) + from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient + from .channels import CoverageChannelsRequestSrc + from .client import Arcmira, AsyncArcmira + from .corrections import SubmitCorrectionsRequestAnchor, SubmitCorrectionsRequestKind + from .entities import ( + LookupEntitiesRequestType, + MomentumEntitiesRequestSrc, + ResolveEntitiesRequestSrc, + ResolveEntitiesRequestType, + SearchEntitiesRequestSrc, + SearchEntitiesRequestType, + ) + from .environment import ArcmiraEnvironment + from .feedback import ( + SubmitFeedbackRequestCorrectionsItem, + SubmitFeedbackRequestCorrectionsItemIssueType, + SubmitFeedbackRequestCorrectionsItemMentionClass, + SubmitFeedbackRequestCorrectionsItemReason, + SubmitFeedbackRequestCorrectionsItemSuggestedChange, + SubmitFeedbackRequestMethod, + SubmitFeedbackRequestType, + ) + from .me import UpdateSettingsMeRequestTranscripts, UpdateSettingsMeRequestTranscriptsQuality + from .mentions import ( + CountMentionsRequestMode, + CountMentionsRequestSrc, + ListMentionsRequestDetails, + ListMentionsRequestEntityType, + ListMentionsRequestSentiment, + ListMentionsRequestSrc, + ) + from .monitors import CreateMonitorsRequestNotifyFrequency, UpdateMonitorsRequestNotifyFrequency + from .recommendations import ( + ListRecommendationsRequestEntityType, + ListRecommendationsRequestMentionClass, + ListRecommendationsRequestSrc, + ) + from .trackers import ( + CreateTrackersRequestEntityType, + CreateTrackersRequestPersonMatchMode, + UpdateTrackersRequestPersonMatchMode, + ) + from .transcripts import ( + CaptionsTranscriptsRequestSrc, + GetTranscriptsRequestQuality, + GetTranscriptsRequestSrc, + ListRequestsTranscriptsRequestSrc, + SearchTranscriptsRequestSource, + SearchTranscriptsRequestSrc, + StatusTranscriptsRequestSrc, + ) +_dynamic_imports: typing.Dict[str, str] = { + "AccountSettings": ".types", + "Alert": ".types", + "AlertEvidenceKind": ".types", + "AlertListResponse": ".types", + "AlertMonitor": ".types", + "AlertTracker": ".types", + "Arcmira": ".client", + "ArcmiraEnvironment": ".environment", + "AsyncArcmira": ".client", + "BadRankingChange": ".types", + "BadRequestError": ".errors", + "CaptionTrack": ".types", + "CaptionsTranscriptsRequestSrc": ".transcripts", + "ChannelCoverageResponse": ".types", + "ChannelCoverageResponseChannel": ".types", + "ChannelCoverageResponseChannelSourceMix": ".types", + "ChannelGuestListResponse": ".types", + "ChannelGuestListResponseExportCapabilities": ".types", + "ChannelGuestListResponseItemsItem": ".types", + "ChannelGuestListResponseItemsItemSentiment": ".types", + "ChannelPageResponse": ".types", + "ChannelPageResponseChannelInfo": ".types", + "ChannelPageResponseEntity": ".types", + "ChannelPageResponseEntityOwner": ".types", + "ChannelPageResponseEntityType": ".types", + "ChannelPageResponseEpisodesByMonthItem": ".types", + "ChannelPageResponseEpisodesItem": ".types", + "ChannelPageResponseEpisodesItemPlatform": ".types", + "ChannelPageResponseEpisodesItemSentiment": ".types", + "ChannelPageResponseEpisodesItemTimestamp": ".types", + "ChannelPageResponseEpisodesItemType": ".types", + "ChannelPageResponseGuestsItem": ".types", + "ChannelPageResponseGuestsItemRole": ".types", + "ChannelPageResponseGuestsItemSentiment": ".types", + "ChannelPageResponseHostsDetailedItem": ".types", + "ChannelPageResponseHostsDetailedItemSentiment": ".types", + "ChannelPageResponseOrganizationsItem": ".types", + "ChannelPageResponseOrganizationsItemSentiment": ".types", + "ChannelPageResponseProductsItem": ".types", + "ChannelPageResponseProductsItemSentiment": ".types", + "ChannelPageResponseRecommendationsSummary": ".types", + "ChannelPageResponseStats": ".types", + "ChannelPageResponseTopicsItem": ".types", + "ChannelPageResponseTopicsItemSentiment": ".types", + "ChannelSponsor": ".types", + "ChannelSponsorEntity": ".types", + "ChannelSponsorSponsorStatus": ".types", + "ChannelSponsorsResponse": ".types", + "ChannelSponsorsResponseAccess": ".types", + "ChannelSponsorsResponseAccessGate": ".types", + "ChannelSponsorsResponseAccessReason": ".types", + "ChannelSponsorsResponseAccessType": ".types", + "ChannelSponsorsResponseAccessUnlock": ".types", + "ChannelSponsorsResponseAccessUnlockAction": ".types", + "ChannelSponsorsResponseChannel": ".types", + "ChannelSponsorsResponseMeta": ".types", + "ChannelVideosResponse": ".types", + "ChannelVideosResponseChannel": ".types", + "ChannelVideosResponseEpisodesItem": ".types", + "ConflictError": ".errors", + "CorrectionAcceptedResponse": ".types", + "CorrectionAcceptedResponseKind": ".types", + "CorrectionSeqMismatchResponse": ".types", + "CountMentionsRequestMode": ".mentions", + "CountMentionsRequestSrc": ".mentions", + "CoverageChannelsRequestSrc": ".channels", + "CreateMonitorsRequestNotifyFrequency": ".monitors", + "CreateTrackersRequestEntityType": ".trackers", + "CreateTrackersRequestPersonMatchMode": ".trackers", + "DefaultAioHttpClient": "._default_clients", + "DefaultAsyncHttpxClient": "._default_clients", + "DeliveryIssueChange": ".types", + "DeliveryIssueChangeChannel": ".types", + "Entity": ".types", + "EntityCard": ".types", + "EntityCardsResponse": ".types", + "EntityChannelListResponse": ".types", + "EntityChannelListResponseExportCapabilities": ".types", + "EntityChannelListResponseItemsItem": ".types", + "EntityDetailRecommendationsSummary": ".types", + "EntityDetailResponse": ".types", + "EntityLookupResponse": ".types", + "EntityMomentumResponse": ".types", + "EntityMomentumResponseAccess": ".types", + "EntityMomentumResponseAccessGate": ".types", + "EntityMomentumResponseAccessReason": ".types", + "EntityMomentumResponseAccessType": ".types", + "EntityMomentumResponseAccessUnlock": ".types", + "EntityMomentumResponseAccessUnlockAction": ".types", + "EntityMomentumResponsePaidVsOrganic": ".types", + "EntityMomentumResponseTopShowsItem": ".types", + "EntityMomentumResponseVerdict": ".types", + "EntityMomentumResponseVolume": ".types", + "EntityOrganizationListResponse": ".types", + "EntityOrganizationListResponseExportCapabilities": ".types", + "EntityOrganizationListResponseItemsItem": ".types", + "EntityOrganizationListResponseItemsItemSentiment": ".types", + "EntityPageMention": ".types", + "EntityPageMentionExcerpt": ".types", + "EntityPageMentionExcerptPublicSourceClass": ".types", + "EntityPageMentionPlatform": ".types", + "EntityPageMentionSentiment": ".types", + "EntityPageMentionType": ".types", + "EntityPeopleListResponse": ".types", + "EntityPeopleListResponseExportCapabilities": ".types", + "EntityPeopleListResponseItemsItem": ".types", + "EntityPeopleListResponseItemsItemSentiment": ".types", + "EntityPeopleListResponsePeopleMode": ".types", + "EntityProductListResponse": ".types", + "EntityProductListResponseExportCapabilities": ".types", + "EntityProductListResponseItemsItem": ".types", + "EntityProductListResponseItemsItemSentiment": ".types", + "EntityRef": ".types", + "EntityResolveResponse": ".types", + "EntityResolveResponseAsk": ".types", + "EntityResolveResponseAskOptionsItem": ".types", + "EntityResolveResponseConfidence": ".types", + "EntitySearchResponse": ".types", + "EntitySearchResult": ".types", + "EntitySearchResultRecommendationsSummary": ".types", + "EntityTopicListResponse": ".types", + "EntityTopicListResponseExportCapabilities": ".types", + "EntityTopicListResponseItemsItem": ".types", + "EntityTopicListResponseItemsItemSentiment": ".types", + "Error": ".types", + "ErrorError": ".types", + "ErrorErrorGate": ".types", + "ErrorErrorReason": ".types", + "ErrorErrorType": ".types", + "ErrorErrorUnlock": ".types", + "ErrorErrorUnlockAction": ".types", + "ExposureMeta": ".types", + "ExposureMetaAccess": ".types", + "ExposureMetaAccessChart": ".types", + "ExposureMetaAccessCls": ".types", + "ExposureMetaAccessFreshness": ".types", + "ExposureMetaAccessLadder": ".types", + "ExposureMetaAccessRows": ".types", + "ExposureMetaAccessRowsEntities": ".types", + "ExposureMetaAccessRowsMedia": ".types", + "ExposureMetaAccessRowsTopics": ".types", + "ExposureMetaAccessUnlock": ".types", + "ExposureMetaAccessUnlockLimitAction": ".types", + "ExposureMetaAccessUnlockSrc": ".types", + "ExposureMetaAccessView": ".types", + "ExposureMetaAccessWithheldItem": ".types", + "ExposureMetaAccessWithheldItemKind": ".types", + "ExposureMetaAccessWithheldItemParam": ".types", + "ExposureMetaAccessWithheldItemSection": ".types", + "ExposureMetaAccessWithheldItemWhat": ".types", + "ExposureMetaCredits": ".types", + "ExposureMetaCreditsOnDemand": ".types", + "ExposureMetaCreditsPlan": ".types", + "ExposureMetaFreeLimit": ".types", + "ExposureMetaLimitAction": ".types", + "ExposureMetaLimits": ".types", + "ExposureMetaRecentPreview": ".types", + "ExposureMetaRecentPreviewExperiment": ".types", + "ExposureMetaRecentPreviewMentions": ".types", + "ExposureMetaRecentPreviewMentionsExperiment": ".types", + "ExposureMetaRecentPreviewMentionsSubject": ".types", + "ExposureMetaRecentPreviewMentionsTeaserItemsItem": ".types", + "ExposureMetaRecentPreviewSubject": ".types", + "ExposureMetaRecentPreviewTeaserItemsItem": ".types", + "ExposureMetaTotals": ".types", + "ExposureMetaUsageLimitType": ".types", + "FeedbackCorrectionResult": ".types", + "FeedbackCorrectionResultRecommendation": ".types", + "FeedbackCorrectionResultRecommendationMedia": ".types", + "FeedbackCorrectionResultRecommendationMediaSourceChannel": ".types", + "FeedbackCorrectionResultStatus": ".types", + "FeedbackReadbackCorrection": ".types", + "FeedbackReadbackCorrectionStatus": ".types", + "FeedbackReadbackResponse": ".types", + "FeedbackReadbackResponseStatus": ".types", + "FeedbackResponse": ".types", + "ForbiddenError": ".errors", + "FreeformSuggestedChange": ".types", + "GetTranscriptsRequestQuality": ".transcripts", + "GetTranscriptsRequestSrc": ".transcripts", + "HealthResponse": ".types", + "HealthResponseStatus": ".types", + "HealthResponseVersion": ".types", + "InternalServerError": ".errors", + "ListMentionsRequestDetails": ".mentions", + "ListMentionsRequestEntityType": ".mentions", + "ListMentionsRequestSentiment": ".mentions", + "ListMentionsRequestSrc": ".mentions", + "ListRecommendationsRequestEntityType": ".recommendations", + "ListRecommendationsRequestMentionClass": ".recommendations", + "ListRecommendationsRequestSrc": ".recommendations", + "ListRequestsTranscriptsRequestSrc": ".transcripts", + "LookupEntitiesRequestType": ".entities", + "MeResponse": ".types", + "MeResponseCredentialKind": ".types", + "MeResponseUsage": ".types", + "MeResponseUsageCredits": ".types", + "MeResponseUsageCreditsOnDemand": ".types", + "MeResponseUsageCreditsPlan": ".types", + "MeResponseUsageHits": ".types", + "MeSettingsResponse": ".types", + "Mention": ".types", + "MentionCountsResponse": ".types", + "MentionCountsResponseMode": ".types", + "MentionCountsResponseRowsItem": ".types", + "MentionCountsResponseSharedItem": ".types", + "MentionCountsResponseSharedItemByChannelItem": ".types", + "MentionListResponse": ".types", + "MentionListResponseEntity": ".types", + "MentionListResponseUnlock": ".types", + "MentionMedia": ".types", + "MentionMediaSourceChannel": ".types", + "MentionRecommendations": ".types", + "MentionSentiment": ".types", + "MergeSuggestionChange": ".types", + "MessageResponse": ".types", + "MissedAlertChange": ".types", + "MissingResultChange": ".types", + "MomentumEntitiesRequestSrc": ".entities", + "Monitor": ".types", + "MonitorAddTrackersResponse": ".types", + "MonitorDeleteResponse": ".types", + "MonitorEmailRecipientsItem": ".types", + "MonitorEmailRecipientsItemInvitationStatus": ".types", + "MonitorEmailRecipientsItemStatus": ".types", + "MonitorListResponse": ".types", + "MonitorListResponseMonitorsItem": ".types", + "MonitorListResponseMonitorsItemSlackIntegration": ".types", + "MonitorMutationResponse": ".types", + "MonitorMutationResponseMonitor": ".types", + "MonitorTrackersResponse": ".types", + "MonitorTrackersResponseTrackersItem": ".types", + "NamedEntityRef": ".types", + "NotFoundError": ".errors", + "OpenApiDocument": ".types", + "OpenApiDocumentInfo": ".types", + "OpenApiDocumentInfoContact": ".types", + "OpenApiDocumentServersItem": ".types", + "OrganizationPageResponse": ".types", + "OrganizationPageResponseChannelsItem": ".types", + "OrganizationPageResponseChannelsItemSentiment": ".types", + "OrganizationPageResponseEntity": ".types", + "OrganizationPageResponseEntityOwnedChannelsItem": ".types", + "OrganizationPageResponseEntityOwnedProductsItem": ".types", + "OrganizationPageResponseEntityType": ".types", + "OrganizationPageResponseMentionsByMonthItem": ".types", + "OrganizationPageResponsePeopleItem": ".types", + "OrganizationPageResponsePeopleItemSentiment": ".types", + "OrganizationPageResponseProductsItem": ".types", + "OrganizationPageResponseProductsItemSentiment": ".types", + "OrganizationPageResponseRoleEdge": ".types", + "OrganizationPageResponseRoleEdgeLabel": ".types", + "OrganizationPageResponseRoleEdgePeopleItem": ".types", + "OrganizationPageResponseRoleEdgeRecentAppearancesItem": ".types", + "OrganizationPageResponseRoleEdgeRole": ".types", + "OrganizationPageResponseStats": ".types", + "OrganizationPageResponseTopicsItem": ".types", + "OrganizationPageResponseTopicsItemSentiment": ".types", + "PaymentRequiredError": ".errors", + "PersonAppearanceListResponse": ".types", + "PersonAppearanceListResponseItemsItem": ".types", + "PersonAppearanceListResponseItemsItemPlatform": ".types", + "PersonAppearanceListResponseItemsItemSentiment": ".types", + "PersonAppearanceListResponseItemsItemType": ".types", + "PersonPageResponse": ".types", + "PersonPageResponseAppearancesByMonthItem": ".types", + "PersonPageResponseAppearancesItem": ".types", + "PersonPageResponseAppearancesItemPlatform": ".types", + "PersonPageResponseAppearancesItemSentiment": ".types", + "PersonPageResponseAppearancesItemType": ".types", + "PersonPageResponseBrandsItem": ".types", + "PersonPageResponseBrandsItemSentiment": ".types", + "PersonPageResponseEntity": ".types", + "PersonPageResponseEntityOwnedChannelsItem": ".types", + "PersonPageResponseEntityOwnedProductsItem": ".types", + "PersonPageResponseMentionsByMonthItem": ".types", + "PersonPageResponseMentionsItem": ".types", + "PersonPageResponseMentionsItemPlatform": ".types", + "PersonPageResponseMentionsItemSentiment": ".types", + "PersonPageResponseMentionsItemType": ".types", + "PersonPageResponsePeopleItem": ".types", + "PersonPageResponsePeopleItemRole": ".types", + "PersonPageResponsePeopleItemSentiment": ".types", + "PersonPageResponseProductsItem": ".types", + "PersonPageResponseProductsItemSentiment": ".types", + "PersonPageResponseRoleEdge": ".types", + "PersonPageResponseRoleEdgeLabel": ".types", + "PersonPageResponseRoleEdgeRole": ".types", + "PersonPageResponseStats": ".types", + "PersonPageResponseTopicsItem": ".types", + "PersonPageResponseTopicsItemSentiment": ".types", + "PreconditionFailedError": ".errors", + "ProductPageResponse": ".types", + "ProductPageResponseChannelsItem": ".types", + "ProductPageResponseChannelsItemSentiment": ".types", + "ProductPageResponseEntity": ".types", + "ProductPageResponseEntityOwner": ".types", + "ProductPageResponseEntityParentOrg": ".types", + "ProductPageResponseEntityType": ".types", + "ProductPageResponseMentionsByMonthItem": ".types", + "ProductPageResponseOpportunities": ".types", + "ProductPageResponseOrganizationsItem": ".types", + "ProductPageResponseOrganizationsItemSentiment": ".types", + "ProductPageResponsePeopleItem": ".types", + "ProductPageResponsePeopleItemSentiment": ".types", + "ProductPageResponseStats": ".types", + "ProductPageResponseTopicsItem": ".types", + "ProductPageResponseTopicsItemSentiment": ".types", + "PublishedExcerpt": ".types", + "PublishedExcerptPublicSourceClass": ".types", + "Recommendation": ".types", + "RecommendationEnrichmentItem": ".types", + "RecommendationListResponse": ".types", + "RecommendationListResponseEntity": ".types", + "RecommendationMedia": ".types", + "RecommendationMediaSourceChannel": ".types", + "ResolveCandidate": ".types", + "ResolveCandidateMatch": ".types", + "ResolveEntitiesRequestSrc": ".entities", + "ResolveEntitiesRequestType": ".entities", + "ResolveSuggestion": ".types", + "ResolveSuggestionMatch": ".types", + "ResolveSuggestionReason": ".types", + "SearchEntitiesRequestSrc": ".entities", + "SearchEntitiesRequestType": ".entities", + "SearchRequestType": ".types", + "SearchResolveResponse": ".types", + "SearchResolveResponseEntity": ".types", + "SearchTranscriptsRequestSource": ".transcripts", + "SearchTranscriptsRequestSrc": ".transcripts", + "ServiceUnavailableError": ".errors", + "SignupSentResponse": ".types", + "SignupSentResponseNext": ".types", + "SignupSentResponseNextMethod": ".types", + "SignupVerifiedResponse": ".types", + "SpeakerIdentificationSubmittedResponse": ".types", + "SpeakerIdentificationSubmittedResponseIdentification": ".types", + "SpeakerIdentificationSubmittedResponseIdentificationEntity": ".types", + "SpeakerIdentificationSubmittedResponseIdentificationStatus": ".types", + "StaleMetadataChange": ".types", + "StatusTranscriptsRequestSrc": ".transcripts", + "SubmitCorrectionsRequestAnchor": ".corrections", + "SubmitCorrectionsRequestKind": ".corrections", + "SubmitFeedbackRequestCorrectionsItem": ".feedback", + "SubmitFeedbackRequestCorrectionsItemIssueType": ".feedback", + "SubmitFeedbackRequestCorrectionsItemMentionClass": ".feedback", + "SubmitFeedbackRequestCorrectionsItemReason": ".feedback", + "SubmitFeedbackRequestCorrectionsItemSuggestedChange": ".feedback", + "SubmitFeedbackRequestMethod": ".feedback", + "SubmitFeedbackRequestType": ".feedback", + "TeamMember": ".types", + "TeamMemberRole": ".types", + "TeamMemberSeatType": ".types", + "TeamMemberSpend": ".types", + "TeamMemberSpendRole": ".types", + "TeamMemberSpendSeatType": ".types", + "TeamMembersResponse": ".types", + "TeamMembersResponseTeam": ".types", + "TeamSpendResponse": ".types", + "TeamUsageEvent": ".types", + "TeamUsageEventsResponse": ".types", + "TooManyRequestsError": ".errors", + "TopicPageResponse": ".types", + "TopicPageResponseChannelsItem": ".types", + "TopicPageResponseChannelsItemSentiment": ".types", + "TopicPageResponseCompaniesItem": ".types", + "TopicPageResponseCompaniesItemSentiment": ".types", + "TopicPageResponseEntity": ".types", + "TopicPageResponseEntityType": ".types", + "TopicPageResponseMentionsByMonthItem": ".types", + "TopicPageResponseProductsItem": ".types", + "TopicPageResponseProductsItemSentiment": ".types", + "TopicPageResponseRelatedTopicsItem": ".types", + "TopicPageResponseRelatedTopicsItemSentiment": ".types", + "TopicPageResponseStats": ".types", + "TopicPageResponseVoicesItem": ".types", + "TopicPageResponseVoicesItemRole": ".types", + "TopicPageResponseVoicesItemSentiment": ".types", + "Tracker": ".types", + "TrackerListResponse": ".types", + "TrackerMutationResponse": ".types", + "TranscriptEditSubmittedResponse": ".types", + "TranscriptEditSubmittedResponseEdit": ".types", + "TranscriptEditSubmittedResponseEditStatus": ".types", + "TranscriptPending": ".types", + "TranscriptPendingPremiumJob": ".types", + "TranscriptPendingQuality": ".types", + "TranscriptPurchaseQuote": ".types", + "TranscriptPurchaseQuoteBillingScope": ".types", + "TranscriptPurchaseQuoteCharge": ".types", + "TranscriptPurchaseQuoteChargeUnit": ".types", + "TranscriptQuote": ".types", + "TranscriptResponse": ".types", + "TranscriptResponseAccess": ".types", + "TranscriptResponseAccessGate": ".types", + "TranscriptResponseAccessReason": ".types", + "TranscriptResponseAccessType": ".types", + "TranscriptResponseAccessUnlock": ".types", + "TranscriptResponseAccessUnlockAction": ".types", + "TranscriptResponseLinesItem": ".types", + "TranscriptResponseParagraphsItem": ".types", + "TranscriptResponsePremiumJob": ".types", + "TranscriptResponseQuality": ".types", + "TranscriptResponseRange": ".types", + "TranscriptResponseSource": ".types", + "TranscriptResponseSpeakersItem": ".types", + "TranscriptResult": ".types", + "TranscriptResult_Pending": ".types", + "TranscriptResult_Ready": ".types", + "TranscriptSearchChunk": ".types", + "TranscriptSearchResponse": ".types", + "TranscriptSearchResponseAccess": ".types", + "TranscriptSearchResponseAccessGate": ".types", + "TranscriptSearchResponseAccessReason": ".types", + "TranscriptSearchResponseAccessType": ".types", + "TranscriptSearchResponseAccessUnlock": ".types", + "TranscriptSearchResponseAccessUnlockAction": ".types", + "TranscriptSearchResponseFilters": ".types", + "TranscriptSearchResponseSearchIndex": ".types", + "TranscriptSearchResponseSearchIndexState": ".types", + "TranscriptSettings": ".types", + "TranscriptSettingsQuality": ".types", + "TranscriptVideo": ".types", + "TranscriptionListResponse": ".types", + "TranscriptionListResponseRequestsItem": ".types", + "TranscriptionRequest": ".types", + "TranscriptionRequestCharge": ".types", + "TranscriptionRequestChargeUnit": ".types", + "TranscriptionRequestQuote": ".types", + "TranscriptionRequestStage": ".types", + "TranscriptionRequestState": ".types", + "TranscriptionRequestStatus": ".types", + "TranscriptionSubmitResponse": ".types", + "UnauthorizedError": ".errors", + "UnprocessableEntityError": ".errors", + "UpdateMonitorsRequestNotifyFrequency": ".monitors", + "UpdateSettingsMeRequestTranscripts": ".me", + "UpdateSettingsMeRequestTranscriptsQuality": ".me", + "UpdateTrackersRequestPersonMatchMode": ".trackers", + "VideoCaptionsResponse": ".types", + "VideoMergeListResponse": ".types", + "VideoMergeListResponseMergesItem": ".types", + "VideoMergeListResponseMergesItemStatus": ".types", + "VideoMergeSubmittedResponse": ".types", + "VideoMergeSubmittedResponseMerge": ".types", + "VideoMergeSubmittedResponseMergeStatus": ".types", + "WebhookSecretRotateResponse": ".types", + "WithdrawnResponse": ".types", + "WrongClassificationChange": ".types", + "WrongClassificationChangeMentionClass": ".types", + "WrongEntityChange": ".types", + "WrongEntityTypeChange": ".types", + "WrongEntityTypeChangeField": ".types", + "channels": ".channels", + "corrections": ".corrections", + "entities": ".entities", + "feedback": ".feedback", + "health": ".health", + "me": ".me", + "mentions": ".mentions", + "meta": ".meta", + "monitors": ".monitors", + "organizations": ".organizations", + "people": ".people", + "products": ".products", + "recommendations": ".recommendations", + "team": ".team", + "topics": ".topics", + "trackers": ".trackers", + "transcripts": ".transcripts", +} + + +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) -homepage = "https://arcmira.com" -docs = "https://arcmira.com/docs" -api_base = "https://api.arcmira.com/v1" -openapi = "https://api.arcmira.com/v1/openapi.json" -llms_txt = "https://arcmira.com/llms.txt" -docs_llms_txt = "https://arcmira.com/docs/llms.txt" __all__ = [ - "homepage", - "docs", - "api_base", - "openapi", - "llms_txt", - "docs_llms_txt", + "AccountSettings", + "Alert", + "AlertEvidenceKind", + "AlertListResponse", + "AlertMonitor", + "AlertTracker", + "Arcmira", + "ArcmiraEnvironment", + "AsyncArcmira", + "BadRankingChange", + "BadRequestError", + "CaptionTrack", + "CaptionsTranscriptsRequestSrc", + "ChannelCoverageResponse", + "ChannelCoverageResponseChannel", + "ChannelCoverageResponseChannelSourceMix", + "ChannelGuestListResponse", + "ChannelGuestListResponseExportCapabilities", + "ChannelGuestListResponseItemsItem", + "ChannelGuestListResponseItemsItemSentiment", + "ChannelPageResponse", + "ChannelPageResponseChannelInfo", + "ChannelPageResponseEntity", + "ChannelPageResponseEntityOwner", + "ChannelPageResponseEntityType", + "ChannelPageResponseEpisodesByMonthItem", + "ChannelPageResponseEpisodesItem", + "ChannelPageResponseEpisodesItemPlatform", + "ChannelPageResponseEpisodesItemSentiment", + "ChannelPageResponseEpisodesItemTimestamp", + "ChannelPageResponseEpisodesItemType", + "ChannelPageResponseGuestsItem", + "ChannelPageResponseGuestsItemRole", + "ChannelPageResponseGuestsItemSentiment", + "ChannelPageResponseHostsDetailedItem", + "ChannelPageResponseHostsDetailedItemSentiment", + "ChannelPageResponseOrganizationsItem", + "ChannelPageResponseOrganizationsItemSentiment", + "ChannelPageResponseProductsItem", + "ChannelPageResponseProductsItemSentiment", + "ChannelPageResponseRecommendationsSummary", + "ChannelPageResponseStats", + "ChannelPageResponseTopicsItem", + "ChannelPageResponseTopicsItemSentiment", + "ChannelSponsor", + "ChannelSponsorEntity", + "ChannelSponsorSponsorStatus", + "ChannelSponsorsResponse", + "ChannelSponsorsResponseAccess", + "ChannelSponsorsResponseAccessGate", + "ChannelSponsorsResponseAccessReason", + "ChannelSponsorsResponseAccessType", + "ChannelSponsorsResponseAccessUnlock", + "ChannelSponsorsResponseAccessUnlockAction", + "ChannelSponsorsResponseChannel", + "ChannelSponsorsResponseMeta", + "ChannelVideosResponse", + "ChannelVideosResponseChannel", + "ChannelVideosResponseEpisodesItem", + "ConflictError", + "CorrectionAcceptedResponse", + "CorrectionAcceptedResponseKind", + "CorrectionSeqMismatchResponse", + "CountMentionsRequestMode", + "CountMentionsRequestSrc", + "CoverageChannelsRequestSrc", + "CreateMonitorsRequestNotifyFrequency", + "CreateTrackersRequestEntityType", + "CreateTrackersRequestPersonMatchMode", + "DefaultAioHttpClient", + "DefaultAsyncHttpxClient", + "DeliveryIssueChange", + "DeliveryIssueChangeChannel", + "Entity", + "EntityCard", + "EntityCardsResponse", + "EntityChannelListResponse", + "EntityChannelListResponseExportCapabilities", + "EntityChannelListResponseItemsItem", + "EntityDetailRecommendationsSummary", + "EntityDetailResponse", + "EntityLookupResponse", + "EntityMomentumResponse", + "EntityMomentumResponseAccess", + "EntityMomentumResponseAccessGate", + "EntityMomentumResponseAccessReason", + "EntityMomentumResponseAccessType", + "EntityMomentumResponseAccessUnlock", + "EntityMomentumResponseAccessUnlockAction", + "EntityMomentumResponsePaidVsOrganic", + "EntityMomentumResponseTopShowsItem", + "EntityMomentumResponseVerdict", + "EntityMomentumResponseVolume", + "EntityOrganizationListResponse", + "EntityOrganizationListResponseExportCapabilities", + "EntityOrganizationListResponseItemsItem", + "EntityOrganizationListResponseItemsItemSentiment", + "EntityPageMention", + "EntityPageMentionExcerpt", + "EntityPageMentionExcerptPublicSourceClass", + "EntityPageMentionPlatform", + "EntityPageMentionSentiment", + "EntityPageMentionType", + "EntityPeopleListResponse", + "EntityPeopleListResponseExportCapabilities", + "EntityPeopleListResponseItemsItem", + "EntityPeopleListResponseItemsItemSentiment", + "EntityPeopleListResponsePeopleMode", + "EntityProductListResponse", + "EntityProductListResponseExportCapabilities", + "EntityProductListResponseItemsItem", + "EntityProductListResponseItemsItemSentiment", + "EntityRef", + "EntityResolveResponse", + "EntityResolveResponseAsk", + "EntityResolveResponseAskOptionsItem", + "EntityResolveResponseConfidence", + "EntitySearchResponse", + "EntitySearchResult", + "EntitySearchResultRecommendationsSummary", + "EntityTopicListResponse", + "EntityTopicListResponseExportCapabilities", + "EntityTopicListResponseItemsItem", + "EntityTopicListResponseItemsItemSentiment", + "Error", + "ErrorError", + "ErrorErrorGate", + "ErrorErrorReason", + "ErrorErrorType", + "ErrorErrorUnlock", + "ErrorErrorUnlockAction", + "ExposureMeta", + "ExposureMetaAccess", + "ExposureMetaAccessChart", + "ExposureMetaAccessCls", + "ExposureMetaAccessFreshness", + "ExposureMetaAccessLadder", + "ExposureMetaAccessRows", + "ExposureMetaAccessRowsEntities", + "ExposureMetaAccessRowsMedia", + "ExposureMetaAccessRowsTopics", + "ExposureMetaAccessUnlock", + "ExposureMetaAccessUnlockLimitAction", + "ExposureMetaAccessUnlockSrc", + "ExposureMetaAccessView", + "ExposureMetaAccessWithheldItem", + "ExposureMetaAccessWithheldItemKind", + "ExposureMetaAccessWithheldItemParam", + "ExposureMetaAccessWithheldItemSection", + "ExposureMetaAccessWithheldItemWhat", + "ExposureMetaCredits", + "ExposureMetaCreditsOnDemand", + "ExposureMetaCreditsPlan", + "ExposureMetaFreeLimit", + "ExposureMetaLimitAction", + "ExposureMetaLimits", + "ExposureMetaRecentPreview", + "ExposureMetaRecentPreviewExperiment", + "ExposureMetaRecentPreviewMentions", + "ExposureMetaRecentPreviewMentionsExperiment", + "ExposureMetaRecentPreviewMentionsSubject", + "ExposureMetaRecentPreviewMentionsTeaserItemsItem", + "ExposureMetaRecentPreviewSubject", + "ExposureMetaRecentPreviewTeaserItemsItem", + "ExposureMetaTotals", + "ExposureMetaUsageLimitType", + "FeedbackCorrectionResult", + "FeedbackCorrectionResultRecommendation", + "FeedbackCorrectionResultRecommendationMedia", + "FeedbackCorrectionResultRecommendationMediaSourceChannel", + "FeedbackCorrectionResultStatus", + "FeedbackReadbackCorrection", + "FeedbackReadbackCorrectionStatus", + "FeedbackReadbackResponse", + "FeedbackReadbackResponseStatus", + "FeedbackResponse", + "ForbiddenError", + "FreeformSuggestedChange", + "GetTranscriptsRequestQuality", + "GetTranscriptsRequestSrc", + "HealthResponse", + "HealthResponseStatus", + "HealthResponseVersion", + "InternalServerError", + "ListMentionsRequestDetails", + "ListMentionsRequestEntityType", + "ListMentionsRequestSentiment", + "ListMentionsRequestSrc", + "ListRecommendationsRequestEntityType", + "ListRecommendationsRequestMentionClass", + "ListRecommendationsRequestSrc", + "ListRequestsTranscriptsRequestSrc", + "LookupEntitiesRequestType", + "MeResponse", + "MeResponseCredentialKind", + "MeResponseUsage", + "MeResponseUsageCredits", + "MeResponseUsageCreditsOnDemand", + "MeResponseUsageCreditsPlan", + "MeResponseUsageHits", + "MeSettingsResponse", + "Mention", + "MentionCountsResponse", + "MentionCountsResponseMode", + "MentionCountsResponseRowsItem", + "MentionCountsResponseSharedItem", + "MentionCountsResponseSharedItemByChannelItem", + "MentionListResponse", + "MentionListResponseEntity", + "MentionListResponseUnlock", + "MentionMedia", + "MentionMediaSourceChannel", + "MentionRecommendations", + "MentionSentiment", + "MergeSuggestionChange", + "MessageResponse", + "MissedAlertChange", + "MissingResultChange", + "MomentumEntitiesRequestSrc", + "Monitor", + "MonitorAddTrackersResponse", + "MonitorDeleteResponse", + "MonitorEmailRecipientsItem", + "MonitorEmailRecipientsItemInvitationStatus", + "MonitorEmailRecipientsItemStatus", + "MonitorListResponse", + "MonitorListResponseMonitorsItem", + "MonitorListResponseMonitorsItemSlackIntegration", + "MonitorMutationResponse", + "MonitorMutationResponseMonitor", + "MonitorTrackersResponse", + "MonitorTrackersResponseTrackersItem", + "NamedEntityRef", + "NotFoundError", + "OpenApiDocument", + "OpenApiDocumentInfo", + "OpenApiDocumentInfoContact", + "OpenApiDocumentServersItem", + "OrganizationPageResponse", + "OrganizationPageResponseChannelsItem", + "OrganizationPageResponseChannelsItemSentiment", + "OrganizationPageResponseEntity", + "OrganizationPageResponseEntityOwnedChannelsItem", + "OrganizationPageResponseEntityOwnedProductsItem", + "OrganizationPageResponseEntityType", + "OrganizationPageResponseMentionsByMonthItem", + "OrganizationPageResponsePeopleItem", + "OrganizationPageResponsePeopleItemSentiment", + "OrganizationPageResponseProductsItem", + "OrganizationPageResponseProductsItemSentiment", + "OrganizationPageResponseRoleEdge", + "OrganizationPageResponseRoleEdgeLabel", + "OrganizationPageResponseRoleEdgePeopleItem", + "OrganizationPageResponseRoleEdgeRecentAppearancesItem", + "OrganizationPageResponseRoleEdgeRole", + "OrganizationPageResponseStats", + "OrganizationPageResponseTopicsItem", + "OrganizationPageResponseTopicsItemSentiment", + "PaymentRequiredError", + "PersonAppearanceListResponse", + "PersonAppearanceListResponseItemsItem", + "PersonAppearanceListResponseItemsItemPlatform", + "PersonAppearanceListResponseItemsItemSentiment", + "PersonAppearanceListResponseItemsItemType", + "PersonPageResponse", + "PersonPageResponseAppearancesByMonthItem", + "PersonPageResponseAppearancesItem", + "PersonPageResponseAppearancesItemPlatform", + "PersonPageResponseAppearancesItemSentiment", + "PersonPageResponseAppearancesItemType", + "PersonPageResponseBrandsItem", + "PersonPageResponseBrandsItemSentiment", + "PersonPageResponseEntity", + "PersonPageResponseEntityOwnedChannelsItem", + "PersonPageResponseEntityOwnedProductsItem", + "PersonPageResponseMentionsByMonthItem", + "PersonPageResponseMentionsItem", + "PersonPageResponseMentionsItemPlatform", + "PersonPageResponseMentionsItemSentiment", + "PersonPageResponseMentionsItemType", + "PersonPageResponsePeopleItem", + "PersonPageResponsePeopleItemRole", + "PersonPageResponsePeopleItemSentiment", + "PersonPageResponseProductsItem", + "PersonPageResponseProductsItemSentiment", + "PersonPageResponseRoleEdge", + "PersonPageResponseRoleEdgeLabel", + "PersonPageResponseRoleEdgeRole", + "PersonPageResponseStats", + "PersonPageResponseTopicsItem", + "PersonPageResponseTopicsItemSentiment", + "PreconditionFailedError", + "ProductPageResponse", + "ProductPageResponseChannelsItem", + "ProductPageResponseChannelsItemSentiment", + "ProductPageResponseEntity", + "ProductPageResponseEntityOwner", + "ProductPageResponseEntityParentOrg", + "ProductPageResponseEntityType", + "ProductPageResponseMentionsByMonthItem", + "ProductPageResponseOpportunities", + "ProductPageResponseOrganizationsItem", + "ProductPageResponseOrganizationsItemSentiment", + "ProductPageResponsePeopleItem", + "ProductPageResponsePeopleItemSentiment", + "ProductPageResponseStats", + "ProductPageResponseTopicsItem", + "ProductPageResponseTopicsItemSentiment", + "PublishedExcerpt", + "PublishedExcerptPublicSourceClass", + "Recommendation", + "RecommendationEnrichmentItem", + "RecommendationListResponse", + "RecommendationListResponseEntity", + "RecommendationMedia", + "RecommendationMediaSourceChannel", + "ResolveCandidate", + "ResolveCandidateMatch", + "ResolveEntitiesRequestSrc", + "ResolveEntitiesRequestType", + "ResolveSuggestion", + "ResolveSuggestionMatch", + "ResolveSuggestionReason", + "SearchEntitiesRequestSrc", + "SearchEntitiesRequestType", + "SearchRequestType", + "SearchResolveResponse", + "SearchResolveResponseEntity", + "SearchTranscriptsRequestSource", + "SearchTranscriptsRequestSrc", + "ServiceUnavailableError", + "SignupSentResponse", + "SignupSentResponseNext", + "SignupSentResponseNextMethod", + "SignupVerifiedResponse", + "SpeakerIdentificationSubmittedResponse", + "SpeakerIdentificationSubmittedResponseIdentification", + "SpeakerIdentificationSubmittedResponseIdentificationEntity", + "SpeakerIdentificationSubmittedResponseIdentificationStatus", + "StaleMetadataChange", + "StatusTranscriptsRequestSrc", + "SubmitCorrectionsRequestAnchor", + "SubmitCorrectionsRequestKind", + "SubmitFeedbackRequestCorrectionsItem", + "SubmitFeedbackRequestCorrectionsItemIssueType", + "SubmitFeedbackRequestCorrectionsItemMentionClass", + "SubmitFeedbackRequestCorrectionsItemReason", + "SubmitFeedbackRequestCorrectionsItemSuggestedChange", + "SubmitFeedbackRequestMethod", + "SubmitFeedbackRequestType", + "TeamMember", + "TeamMemberRole", + "TeamMemberSeatType", + "TeamMemberSpend", + "TeamMemberSpendRole", + "TeamMemberSpendSeatType", + "TeamMembersResponse", + "TeamMembersResponseTeam", + "TeamSpendResponse", + "TeamUsageEvent", + "TeamUsageEventsResponse", + "TooManyRequestsError", + "TopicPageResponse", + "TopicPageResponseChannelsItem", + "TopicPageResponseChannelsItemSentiment", + "TopicPageResponseCompaniesItem", + "TopicPageResponseCompaniesItemSentiment", + "TopicPageResponseEntity", + "TopicPageResponseEntityType", + "TopicPageResponseMentionsByMonthItem", + "TopicPageResponseProductsItem", + "TopicPageResponseProductsItemSentiment", + "TopicPageResponseRelatedTopicsItem", + "TopicPageResponseRelatedTopicsItemSentiment", + "TopicPageResponseStats", + "TopicPageResponseVoicesItem", + "TopicPageResponseVoicesItemRole", + "TopicPageResponseVoicesItemSentiment", + "Tracker", + "TrackerListResponse", + "TrackerMutationResponse", + "TranscriptEditSubmittedResponse", + "TranscriptEditSubmittedResponseEdit", + "TranscriptEditSubmittedResponseEditStatus", + "TranscriptPending", + "TranscriptPendingPremiumJob", + "TranscriptPendingQuality", + "TranscriptPurchaseQuote", + "TranscriptPurchaseQuoteBillingScope", + "TranscriptPurchaseQuoteCharge", + "TranscriptPurchaseQuoteChargeUnit", + "TranscriptQuote", + "TranscriptResponse", + "TranscriptResponseAccess", + "TranscriptResponseAccessGate", + "TranscriptResponseAccessReason", + "TranscriptResponseAccessType", + "TranscriptResponseAccessUnlock", + "TranscriptResponseAccessUnlockAction", + "TranscriptResponseLinesItem", + "TranscriptResponseParagraphsItem", + "TranscriptResponsePremiumJob", + "TranscriptResponseQuality", + "TranscriptResponseRange", + "TranscriptResponseSource", + "TranscriptResponseSpeakersItem", + "TranscriptResult", + "TranscriptResult_Pending", + "TranscriptResult_Ready", + "TranscriptSearchChunk", + "TranscriptSearchResponse", + "TranscriptSearchResponseAccess", + "TranscriptSearchResponseAccessGate", + "TranscriptSearchResponseAccessReason", + "TranscriptSearchResponseAccessType", + "TranscriptSearchResponseAccessUnlock", + "TranscriptSearchResponseAccessUnlockAction", + "TranscriptSearchResponseFilters", + "TranscriptSearchResponseSearchIndex", + "TranscriptSearchResponseSearchIndexState", + "TranscriptSettings", + "TranscriptSettingsQuality", + "TranscriptVideo", + "TranscriptionListResponse", + "TranscriptionListResponseRequestsItem", + "TranscriptionRequest", + "TranscriptionRequestCharge", + "TranscriptionRequestChargeUnit", + "TranscriptionRequestQuote", + "TranscriptionRequestStage", + "TranscriptionRequestState", + "TranscriptionRequestStatus", + "TranscriptionSubmitResponse", + "UnauthorizedError", + "UnprocessableEntityError", + "UpdateMonitorsRequestNotifyFrequency", + "UpdateSettingsMeRequestTranscripts", + "UpdateSettingsMeRequestTranscriptsQuality", + "UpdateTrackersRequestPersonMatchMode", + "VideoCaptionsResponse", + "VideoMergeListResponse", + "VideoMergeListResponseMergesItem", + "VideoMergeListResponseMergesItemStatus", + "VideoMergeSubmittedResponse", + "VideoMergeSubmittedResponseMerge", + "VideoMergeSubmittedResponseMergeStatus", + "WebhookSecretRotateResponse", + "WithdrawnResponse", + "WrongClassificationChange", + "WrongClassificationChangeMentionClass", + "WrongEntityChange", + "WrongEntityTypeChange", + "WrongEntityTypeChangeField", + "channels", + "corrections", + "entities", + "feedback", + "health", + "me", + "mentions", + "meta", + "monitors", + "organizations", + "people", + "products", + "recommendations", + "team", + "topics", + "trackers", + "transcripts", ] + +from ._package import __version__, homepage, docs, api_base, openapi, llms_txt, docs_llms_txt diff --git a/src/arcmira/_default_clients.py b/src/arcmira/_default_clients.py new file mode 100644 index 0000000..c87f0ca --- /dev/null +++ b/src/arcmira/_default_clients.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +SDK_DEFAULT_TIMEOUT = 60 + +try: + import httpx_aiohttp # type: ignore[import-not-found] +except ImportError: + + class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + raise RuntimeError("To use the aiohttp client, install the aiohttp extra: pip install arcmira[aiohttp]") + +else: + + class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +class DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) diff --git a/src/arcmira/_package.py b/src/arcmira/_package.py new file mode 100644 index 0000000..a2edf53 --- /dev/null +++ b/src/arcmira/_package.py @@ -0,0 +1,8 @@ +# Written by scripts/install-generated.py from VERSION. +__version__ = '0.3.0' +homepage = "https://arcmira.com" +docs = "https://arcmira.com/docs" +api_base = "https://api.arcmira.com/v1" +openapi = "https://api.arcmira.com/v1/openapi.json" +llms_txt = "https://arcmira.com/llms.txt" +docs_llms_txt = "https://arcmira.com/docs/llms.txt" diff --git a/src/arcmira/channels/__init__.py b/src/arcmira/channels/__init__.py new file mode 100644 index 0000000..aeda67b --- /dev/null +++ b/src/arcmira/channels/__init__.py @@ -0,0 +1,109 @@ +# 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 CoverageChannelsRequestSrc + from . import guests, related, sponsors, videos + from .guests import ListGuestsRequestIsAppearance, ListGuestsRequestMode, ListGuestsRequestOrder + from .related import ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) + from .sponsors import ListSponsorsRequestSrc, ListSponsorsRequestStatus + from .videos import ListVideosRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".related", + "ChannelsRelatedRequestMode": ".related", + "ChannelsRelatedRequestOrder": ".related", + "CoverageChannelsRequestSrc": ".types", + "ListGuestsRequestIsAppearance": ".guests", + "ListGuestsRequestMode": ".guests", + "ListGuestsRequestOrder": ".guests", + "ListSponsorsRequestSrc": ".sponsors", + "ListSponsorsRequestStatus": ".sponsors", + "ListVideosRequestSrc": ".videos", + "OrganizationsRelatedRequestIsAppearance": ".related", + "OrganizationsRelatedRequestMode": ".related", + "OrganizationsRelatedRequestOrder": ".related", + "PeopleRelatedRequestIsAppearance": ".related", + "PeopleRelatedRequestMode": ".related", + "PeopleRelatedRequestOrder": ".related", + "ProductsRelatedRequestIsAppearance": ".related", + "ProductsRelatedRequestMode": ".related", + "ProductsRelatedRequestOrder": ".related", + "TopicsRelatedRequestIsAppearance": ".related", + "TopicsRelatedRequestMode": ".related", + "TopicsRelatedRequestOrder": ".related", + "guests": ".guests", + "related": ".related", + "sponsors": ".sponsors", + "videos": ".videos", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "CoverageChannelsRequestSrc", + "ListGuestsRequestIsAppearance", + "ListGuestsRequestMode", + "ListGuestsRequestOrder", + "ListSponsorsRequestSrc", + "ListSponsorsRequestStatus", + "ListVideosRequestSrc", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", + "guests", + "related", + "sponsors", + "videos", +] diff --git a/src/arcmira/channels/client.py b/src/arcmira/channels/client.py new file mode 100644 index 0000000..a375349 --- /dev/null +++ b/src/arcmira/channels/client.py @@ -0,0 +1,282 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.channel_coverage_response import ChannelCoverageResponse +from ..types.channel_page_response import ChannelPageResponse +from .raw_client import AsyncRawChannelsClient, RawChannelsClient +from .types.coverage_channels_request_src import CoverageChannelsRequestSrc + +if typing.TYPE_CHECKING: + from .guests.client import AsyncGuestsClient, GuestsClient + from .related.client import AsyncRelatedClient, RelatedClient + from .sponsors.client import AsyncSponsorsClient, SponsorsClient + from .videos.client import AsyncVideosClient, VideosClient + + +class ChannelsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawChannelsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._sponsors: typing.Optional[SponsorsClient] = None + self._videos: typing.Optional[VideosClient] = None + self._related: typing.Optional[RelatedClient] = None + self._guests: typing.Optional[GuestsClient] = None + + @property + def with_raw_response(self) -> RawChannelsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawChannelsClient + """ + return self._raw_client + + def coverage( + self, + channel_id: str, + *, + src: typing.Optional[CoverageChannelsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ChannelCoverageResponse: + """ + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + src : typing.Optional[CoverageChannelsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ChannelCoverageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.channels.coverage( + channel_id="channel_id", + ) + """ + _response = self._raw_client.coverage(channel_id, src=src, request_options=request_options) + return _response.data + + def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> ChannelPageResponse: + """ + Channel pages include a recommendations_summary teaser: sponsor_count for all callers; top_sponsors additionally requires a Pro+ plan. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ChannelPageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.channels.get( + slug="slug", + ) + """ + _response = self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def sponsors(self): + if self._sponsors is None: + from .sponsors.client import SponsorsClient # noqa: E402 + + self._sponsors = SponsorsClient(client_wrapper=self._client_wrapper) + return self._sponsors + + @property + def videos(self): + if self._videos is None: + from .videos.client import VideosClient # noqa: E402 + + self._videos = VideosClient(client_wrapper=self._client_wrapper) + return self._videos + + @property + def related(self): + if self._related is None: + from .related.client import RelatedClient # noqa: E402 + + self._related = RelatedClient(client_wrapper=self._client_wrapper) + return self._related + + @property + def guests(self): + if self._guests is None: + from .guests.client import GuestsClient # noqa: E402 + + self._guests = GuestsClient(client_wrapper=self._client_wrapper) + return self._guests + + +class AsyncChannelsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawChannelsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._sponsors: typing.Optional[AsyncSponsorsClient] = None + self._videos: typing.Optional[AsyncVideosClient] = None + self._related: typing.Optional[AsyncRelatedClient] = None + self._guests: typing.Optional[AsyncGuestsClient] = None + + @property + def with_raw_response(self) -> AsyncRawChannelsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawChannelsClient + """ + return self._raw_client + + async def coverage( + self, + channel_id: str, + *, + src: typing.Optional[CoverageChannelsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ChannelCoverageResponse: + """ + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + src : typing.Optional[CoverageChannelsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ChannelCoverageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.channels.coverage( + channel_id="channel_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.coverage(channel_id, src=src, request_options=request_options) + return _response.data + + async def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> ChannelPageResponse: + """ + Channel pages include a recommendations_summary teaser: sponsor_count for all callers; top_sponsors additionally requires a Pro+ plan. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ChannelPageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.channels.get( + slug="slug", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def sponsors(self): + if self._sponsors is None: + from .sponsors.client import AsyncSponsorsClient # noqa: E402 + + self._sponsors = AsyncSponsorsClient(client_wrapper=self._client_wrapper) + return self._sponsors + + @property + def videos(self): + if self._videos is None: + from .videos.client import AsyncVideosClient # noqa: E402 + + self._videos = AsyncVideosClient(client_wrapper=self._client_wrapper) + return self._videos + + @property + def related(self): + if self._related is None: + from .related.client import AsyncRelatedClient # noqa: E402 + + self._related = AsyncRelatedClient(client_wrapper=self._client_wrapper) + return self._related + + @property + def guests(self): + if self._guests is None: + from .guests.client import AsyncGuestsClient # noqa: E402 + + self._guests = AsyncGuestsClient(client_wrapper=self._client_wrapper) + return self._guests diff --git a/src/arcmira/channels/guests/__init__.py b/src/arcmira/channels/guests/__init__.py new file mode 100644 index 0000000..a406741 --- /dev/null +++ b/src/arcmira/channels/guests/__init__.py @@ -0,0 +1,38 @@ +# 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 ListGuestsRequestIsAppearance, ListGuestsRequestMode, ListGuestsRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ListGuestsRequestIsAppearance": ".types", + "ListGuestsRequestMode": ".types", + "ListGuestsRequestOrder": ".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__ = ["ListGuestsRequestIsAppearance", "ListGuestsRequestMode", "ListGuestsRequestOrder"] diff --git a/src/arcmira/channels/guests/client.py b/src/arcmira/channels/guests/client.py new file mode 100644 index 0000000..cf8620e --- /dev/null +++ b/src/arcmira/channels/guests/client.py @@ -0,0 +1,218 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.channel_guest_list_response import ChannelGuestListResponse +from ...types.channel_guest_list_response_items_item import ChannelGuestListResponseItemsItem +from .raw_client import AsyncRawGuestsClient, RawGuestsClient +from .types.list_guests_request_is_appearance import ListGuestsRequestIsAppearance +from .types.list_guests_request_mode import ListGuestsRequestMode +from .types.list_guests_request_order import ListGuestsRequestOrder + + +class GuestsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawGuestsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawGuestsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawGuestsClient + """ + return self._raw_client + + def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListGuestsRequestOrder] = None, + mode: typing.Optional[ListGuestsRequestMode] = None, + is_appearance: typing.Optional[ListGuestsRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: + """ + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListGuestsRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListGuestsRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListGuestsRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.guests.list( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncGuestsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawGuestsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawGuestsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawGuestsClient + """ + return self._raw_client + + async def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListGuestsRequestOrder] = None, + mode: typing.Optional[ListGuestsRequestMode] = None, + is_appearance: typing.Optional[ListGuestsRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: + """ + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListGuestsRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListGuestsRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListGuestsRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.guests.list( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/channels/guests/raw_client.py b/src/arcmira/channels/guests/raw_client.py new file mode 100644 index 0000000..4337289 --- /dev/null +++ b/src/arcmira/channels/guests/raw_client.py @@ -0,0 +1,397 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.channel_guest_list_response import ChannelGuestListResponse +from ...types.channel_guest_list_response_items_item import ChannelGuestListResponseItemsItem +from ...types.error import Error +from .types.list_guests_request_is_appearance import ListGuestsRequestIsAppearance +from .types.list_guests_request_mode import ListGuestsRequestMode +from .types.list_guests_request_order import ListGuestsRequestOrder +from pydantic import ValidationError + + +class RawGuestsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListGuestsRequestOrder] = None, + mode: typing.Optional[ListGuestsRequestMode] = None, + is_appearance: typing.Optional[ListGuestsRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: + """ + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListGuestsRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListGuestsRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListGuestsRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/guests", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + ChannelGuestListResponse, + parse_obj_as( + type_=ChannelGuestListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawGuestsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListGuestsRequestOrder] = None, + mode: typing.Optional[ListGuestsRequestMode] = None, + is_appearance: typing.Optional[ListGuestsRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: + """ + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListGuestsRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListGuestsRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListGuestsRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/guests", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + ChannelGuestListResponse, + parse_obj_as( + type_=ChannelGuestListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/channels/guests/types/__init__.py b/src/arcmira/channels/guests/types/__init__.py new file mode 100644 index 0000000..f6546f5 --- /dev/null +++ b/src/arcmira/channels/guests/types/__init__.py @@ -0,0 +1,40 @@ +# 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 .list_guests_request_is_appearance import ListGuestsRequestIsAppearance + from .list_guests_request_mode import ListGuestsRequestMode + from .list_guests_request_order import ListGuestsRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ListGuestsRequestIsAppearance": ".list_guests_request_is_appearance", + "ListGuestsRequestMode": ".list_guests_request_mode", + "ListGuestsRequestOrder": ".list_guests_request_order", +} + + +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__ = ["ListGuestsRequestIsAppearance", "ListGuestsRequestMode", "ListGuestsRequestOrder"] diff --git a/src/arcmira/channels/guests/types/list_guests_request_is_appearance.py b/src/arcmira/channels/guests/types/list_guests_request_is_appearance.py new file mode 100644 index 0000000..472baf1 --- /dev/null +++ b/src/arcmira/channels/guests/types/list_guests_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListGuestsRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/channels/guests/types/list_guests_request_mode.py b/src/arcmira/channels/guests/types/list_guests_request_mode.py new file mode 100644 index 0000000..f4e8b9d --- /dev/null +++ b/src/arcmira/channels/guests/types/list_guests_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListGuestsRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/channels/guests/types/list_guests_request_order.py b/src/arcmira/channels/guests/types/list_guests_request_order.py new file mode 100644 index 0000000..acfff67 --- /dev/null +++ b/src/arcmira/channels/guests/types/list_guests_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListGuestsRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/channels/raw_client.py b/src/arcmira/channels/raw_client.py new file mode 100644 index 0000000..9551395 --- /dev/null +++ b/src/arcmira/channels/raw_client.py @@ -0,0 +1,512 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.channel_coverage_response import ChannelCoverageResponse +from ..types.channel_page_response import ChannelPageResponse +from ..types.error import Error +from .types.coverage_channels_request_src import CoverageChannelsRequestSrc +from pydantic import ValidationError + + +class RawChannelsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def coverage( + self, + channel_id: str, + *, + src: typing.Optional[CoverageChannelsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ChannelCoverageResponse]: + """ + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + src : typing.Optional[CoverageChannelsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ChannelCoverageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(channel_id)}/coverage", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ChannelCoverageResponse, + parse_obj_as( + type_=ChannelCoverageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ChannelPageResponse]: + """ + Channel pages include a recommendations_summary teaser: sponsor_count for all callers; top_sponsors additionally requires a Pro+ plan. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ChannelPageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ChannelPageResponse, + parse_obj_as( + type_=ChannelPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawChannelsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def coverage( + self, + channel_id: str, + *, + src: typing.Optional[CoverageChannelsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ChannelCoverageResponse]: + """ + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + src : typing.Optional[CoverageChannelsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ChannelCoverageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(channel_id)}/coverage", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ChannelCoverageResponse, + parse_obj_as( + type_=ChannelCoverageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ChannelPageResponse]: + """ + Channel pages include a recommendations_summary teaser: sponsor_count for all callers; top_sponsors additionally requires a Pro+ plan. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ChannelPageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ChannelPageResponse, + parse_obj_as( + type_=ChannelPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/channels/related/__init__.py b/src/arcmira/channels/related/__init__.py new file mode 100644 index 0000000..bed85d9 --- /dev/null +++ b/src/arcmira/channels/related/__init__.py @@ -0,0 +1,82 @@ +# 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 ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".types", + "ChannelsRelatedRequestMode": ".types", + "ChannelsRelatedRequestOrder": ".types", + "OrganizationsRelatedRequestIsAppearance": ".types", + "OrganizationsRelatedRequestMode": ".types", + "OrganizationsRelatedRequestOrder": ".types", + "PeopleRelatedRequestIsAppearance": ".types", + "PeopleRelatedRequestMode": ".types", + "PeopleRelatedRequestOrder": ".types", + "ProductsRelatedRequestIsAppearance": ".types", + "ProductsRelatedRequestMode": ".types", + "ProductsRelatedRequestOrder": ".types", + "TopicsRelatedRequestIsAppearance": ".types", + "TopicsRelatedRequestMode": ".types", + "TopicsRelatedRequestOrder": ".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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/channels/related/client.py b/src/arcmira/channels/related/client.py new file mode 100644 index 0000000..af626a3 --- /dev/null +++ b/src/arcmira/channels/related/client.py @@ -0,0 +1,930 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from .raw_client import AsyncRawRelatedClient, RawRelatedClient +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder + + +class RelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRelatedClient + """ + return self._raw_client + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.related.topics( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.related.people( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.related.organizations( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.related.products( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.related.channels( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRelatedClient + """ + return self._raw_client + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.related.topics( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.related.people( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.related.organizations( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.related.products( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.related.channels( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/channels/related/raw_client.py b/src/arcmira/channels/related/raw_client.py new file mode 100644 index 0000000..b765479 --- /dev/null +++ b/src/arcmira/channels/related/raw_client.py @@ -0,0 +1,1861 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from ...types.error import Error +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder +from pydantic import ValidationError + + +class RawRelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/channels/related/types/__init__.py b/src/arcmira/channels/related/types/__init__.py new file mode 100644 index 0000000..1f3a8a7 --- /dev/null +++ b/src/arcmira/channels/related/types/__init__.py @@ -0,0 +1,80 @@ +# 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 .channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance + from .channels_related_request_mode import ChannelsRelatedRequestMode + from .channels_related_request_order import ChannelsRelatedRequestOrder + from .organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance + from .organizations_related_request_mode import OrganizationsRelatedRequestMode + from .organizations_related_request_order import OrganizationsRelatedRequestOrder + from .people_related_request_is_appearance import PeopleRelatedRequestIsAppearance + from .people_related_request_mode import PeopleRelatedRequestMode + from .people_related_request_order import PeopleRelatedRequestOrder + from .products_related_request_is_appearance import ProductsRelatedRequestIsAppearance + from .products_related_request_mode import ProductsRelatedRequestMode + from .products_related_request_order import ProductsRelatedRequestOrder + from .topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance + from .topics_related_request_mode import TopicsRelatedRequestMode + from .topics_related_request_order import TopicsRelatedRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".channels_related_request_is_appearance", + "ChannelsRelatedRequestMode": ".channels_related_request_mode", + "ChannelsRelatedRequestOrder": ".channels_related_request_order", + "OrganizationsRelatedRequestIsAppearance": ".organizations_related_request_is_appearance", + "OrganizationsRelatedRequestMode": ".organizations_related_request_mode", + "OrganizationsRelatedRequestOrder": ".organizations_related_request_order", + "PeopleRelatedRequestIsAppearance": ".people_related_request_is_appearance", + "PeopleRelatedRequestMode": ".people_related_request_mode", + "PeopleRelatedRequestOrder": ".people_related_request_order", + "ProductsRelatedRequestIsAppearance": ".products_related_request_is_appearance", + "ProductsRelatedRequestMode": ".products_related_request_mode", + "ProductsRelatedRequestOrder": ".products_related_request_order", + "TopicsRelatedRequestIsAppearance": ".topics_related_request_is_appearance", + "TopicsRelatedRequestMode": ".topics_related_request_mode", + "TopicsRelatedRequestOrder": ".topics_related_request_order", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/channels/related/types/channels_related_request_is_appearance.py b/src/arcmira/channels/related/types/channels_related_request_is_appearance.py new file mode 100644 index 0000000..e21cd2c --- /dev/null +++ b/src/arcmira/channels/related/types/channels_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/channels/related/types/channels_related_request_mode.py b/src/arcmira/channels/related/types/channels_related_request_mode.py new file mode 100644 index 0000000..49a4137 --- /dev/null +++ b/src/arcmira/channels/related/types/channels_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/channels/related/types/channels_related_request_order.py b/src/arcmira/channels/related/types/channels_related_request_order.py new file mode 100644 index 0000000..0f5b302 --- /dev/null +++ b/src/arcmira/channels/related/types/channels_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/channels/related/types/organizations_related_request_is_appearance.py b/src/arcmira/channels/related/types/organizations_related_request_is_appearance.py new file mode 100644 index 0000000..2762d60 --- /dev/null +++ b/src/arcmira/channels/related/types/organizations_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/channels/related/types/organizations_related_request_mode.py b/src/arcmira/channels/related/types/organizations_related_request_mode.py new file mode 100644 index 0000000..2bdd64b --- /dev/null +++ b/src/arcmira/channels/related/types/organizations_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/channels/related/types/organizations_related_request_order.py b/src/arcmira/channels/related/types/organizations_related_request_order.py new file mode 100644 index 0000000..4c5dde2 --- /dev/null +++ b/src/arcmira/channels/related/types/organizations_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/channels/related/types/people_related_request_is_appearance.py b/src/arcmira/channels/related/types/people_related_request_is_appearance.py new file mode 100644 index 0000000..af591fc --- /dev/null +++ b/src/arcmira/channels/related/types/people_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/channels/related/types/people_related_request_mode.py b/src/arcmira/channels/related/types/people_related_request_mode.py new file mode 100644 index 0000000..9d9b51c --- /dev/null +++ b/src/arcmira/channels/related/types/people_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/channels/related/types/people_related_request_order.py b/src/arcmira/channels/related/types/people_related_request_order.py new file mode 100644 index 0000000..a0d19ad --- /dev/null +++ b/src/arcmira/channels/related/types/people_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/channels/related/types/products_related_request_is_appearance.py b/src/arcmira/channels/related/types/products_related_request_is_appearance.py new file mode 100644 index 0000000..a6aba02 --- /dev/null +++ b/src/arcmira/channels/related/types/products_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/channels/related/types/products_related_request_mode.py b/src/arcmira/channels/related/types/products_related_request_mode.py new file mode 100644 index 0000000..9046641 --- /dev/null +++ b/src/arcmira/channels/related/types/products_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/channels/related/types/products_related_request_order.py b/src/arcmira/channels/related/types/products_related_request_order.py new file mode 100644 index 0000000..3e5eb9f --- /dev/null +++ b/src/arcmira/channels/related/types/products_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/channels/related/types/topics_related_request_is_appearance.py b/src/arcmira/channels/related/types/topics_related_request_is_appearance.py new file mode 100644 index 0000000..2b7c65e --- /dev/null +++ b/src/arcmira/channels/related/types/topics_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/channels/related/types/topics_related_request_mode.py b/src/arcmira/channels/related/types/topics_related_request_mode.py new file mode 100644 index 0000000..090ee69 --- /dev/null +++ b/src/arcmira/channels/related/types/topics_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/channels/related/types/topics_related_request_order.py b/src/arcmira/channels/related/types/topics_related_request_order.py new file mode 100644 index 0000000..56c645a --- /dev/null +++ b/src/arcmira/channels/related/types/topics_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/channels/sponsors/__init__.py b/src/arcmira/channels/sponsors/__init__.py new file mode 100644 index 0000000..60eaa53 --- /dev/null +++ b/src/arcmira/channels/sponsors/__init__.py @@ -0,0 +1,34 @@ +# 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 ListSponsorsRequestSrc, ListSponsorsRequestStatus +_dynamic_imports: typing.Dict[str, str] = {"ListSponsorsRequestSrc": ".types", "ListSponsorsRequestStatus": ".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__ = ["ListSponsorsRequestSrc", "ListSponsorsRequestStatus"] diff --git a/src/arcmira/channels/sponsors/client.py b/src/arcmira/channels/sponsors/client.py new file mode 100644 index 0000000..3f85cd4 --- /dev/null +++ b/src/arcmira/channels/sponsors/client.py @@ -0,0 +1,158 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.channel_sponsors_response import ChannelSponsorsResponse +from .raw_client import AsyncRawSponsorsClient, RawSponsorsClient +from .types.list_sponsors_request_src import ListSponsorsRequestSrc +from .types.list_sponsors_request_status import ListSponsorsRequestStatus + + +class SponsorsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSponsorsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSponsorsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSponsorsClient + """ + return self._raw_client + + def list( + self, + channel_id: str, + *, + min_ad_reads: typing.Optional[int] = None, + status: typing.Optional[ListSponsorsRequestStatus] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[ListSponsorsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ChannelSponsorsResponse: + """ + Rollup of recurring sponsors for a YouTube channel, ordered by ad read count. On a Pro+ plan the full list is served and min_ad_reads (default 3), status, and limit apply. Every other plan receives the free slice an anonymous visitor sees on arcmira.com, with meta.total naming the true count and access naming the gate; passing min_ad_reads, status, or limit on such a plan is refused with filter_requires_paid. Pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + min_ad_reads : typing.Optional[int] + Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid. + + status : typing.Optional[ListSponsorsRequestStatus] + Filter against the curated known-advertisers dataset. Pro+ only. + + limit : typing.Optional[int] + Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. + + src : typing.Optional[ListSponsorsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ChannelSponsorsResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.channels.sponsors.list( + channel_id="channel_id", + ) + """ + _response = self._raw_client.list( + channel_id, min_ad_reads=min_ad_reads, status=status, limit=limit, src=src, request_options=request_options + ) + return _response.data + + +class AsyncSponsorsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSponsorsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSponsorsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSponsorsClient + """ + return self._raw_client + + async def list( + self, + channel_id: str, + *, + min_ad_reads: typing.Optional[int] = None, + status: typing.Optional[ListSponsorsRequestStatus] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[ListSponsorsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ChannelSponsorsResponse: + """ + Rollup of recurring sponsors for a YouTube channel, ordered by ad read count. On a Pro+ plan the full list is served and min_ad_reads (default 3), status, and limit apply. Every other plan receives the free slice an anonymous visitor sees on arcmira.com, with meta.total naming the true count and access naming the gate; passing min_ad_reads, status, or limit on such a plan is refused with filter_requires_paid. Pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + min_ad_reads : typing.Optional[int] + Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid. + + status : typing.Optional[ListSponsorsRequestStatus] + Filter against the curated known-advertisers dataset. Pro+ only. + + limit : typing.Optional[int] + Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. + + src : typing.Optional[ListSponsorsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ChannelSponsorsResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.channels.sponsors.list( + channel_id="channel_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + channel_id, min_ad_reads=min_ad_reads, status=status, limit=limit, src=src, request_options=request_options + ) + return _response.data diff --git a/src/arcmira/channels/sponsors/raw_client.py b/src/arcmira/channels/sponsors/raw_client.py new file mode 100644 index 0000000..8f01e48 --- /dev/null +++ b/src/arcmira/channels/sponsors/raw_client.py @@ -0,0 +1,324 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.channel_sponsors_response import ChannelSponsorsResponse +from ...types.error import Error +from .types.list_sponsors_request_src import ListSponsorsRequestSrc +from .types.list_sponsors_request_status import ListSponsorsRequestStatus +from pydantic import ValidationError + + +class RawSponsorsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + channel_id: str, + *, + min_ad_reads: typing.Optional[int] = None, + status: typing.Optional[ListSponsorsRequestStatus] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[ListSponsorsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ChannelSponsorsResponse]: + """ + Rollup of recurring sponsors for a YouTube channel, ordered by ad read count. On a Pro+ plan the full list is served and min_ad_reads (default 3), status, and limit apply. Every other plan receives the free slice an anonymous visitor sees on arcmira.com, with meta.total naming the true count and access naming the gate; passing min_ad_reads, status, or limit on such a plan is refused with filter_requires_paid. Pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + min_ad_reads : typing.Optional[int] + Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid. + + status : typing.Optional[ListSponsorsRequestStatus] + Filter against the curated known-advertisers dataset. Pro+ only. + + limit : typing.Optional[int] + Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. + + src : typing.Optional[ListSponsorsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ChannelSponsorsResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(channel_id)}/sponsors", + method="GET", + params={ + "min_ad_reads": min_ad_reads, + "status": status, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ChannelSponsorsResponse, + parse_obj_as( + type_=ChannelSponsorsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawSponsorsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + channel_id: str, + *, + min_ad_reads: typing.Optional[int] = None, + status: typing.Optional[ListSponsorsRequestStatus] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[ListSponsorsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ChannelSponsorsResponse]: + """ + Rollup of recurring sponsors for a YouTube channel, ordered by ad read count. On a Pro+ plan the full list is served and min_ad_reads (default 3), status, and limit apply. Every other plan receives the free slice an anonymous visitor sees on arcmira.com, with meta.total naming the true count and access naming the gate; passing min_ad_reads, status, or limit on such a plan is refused with filter_requires_paid. Pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + min_ad_reads : typing.Optional[int] + Sponsors with fewer ad reads are excluded. Default 3. Pro+ only; on other plans passing it is refused with filter_requires_paid. + + status : typing.Optional[ListSponsorsRequestStatus] + Filter against the curated known-advertisers dataset. Pro+ only. + + limit : typing.Optional[int] + Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. + + src : typing.Optional[ListSponsorsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ChannelSponsorsResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(channel_id)}/sponsors", + method="GET", + params={ + "min_ad_reads": min_ad_reads, + "status": status, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ChannelSponsorsResponse, + parse_obj_as( + type_=ChannelSponsorsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/channels/sponsors/types/__init__.py b/src/arcmira/channels/sponsors/types/__init__.py new file mode 100644 index 0000000..ae4987f --- /dev/null +++ b/src/arcmira/channels/sponsors/types/__init__.py @@ -0,0 +1,38 @@ +# 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 .list_sponsors_request_src import ListSponsorsRequestSrc + from .list_sponsors_request_status import ListSponsorsRequestStatus +_dynamic_imports: typing.Dict[str, str] = { + "ListSponsorsRequestSrc": ".list_sponsors_request_src", + "ListSponsorsRequestStatus": ".list_sponsors_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__ = ["ListSponsorsRequestSrc", "ListSponsorsRequestStatus"] diff --git a/src/arcmira/channels/sponsors/types/list_sponsors_request_src.py b/src/arcmira/channels/sponsors/types/list_sponsors_request_src.py new file mode 100644 index 0000000..d77e06d --- /dev/null +++ b/src/arcmira/channels/sponsors/types/list_sponsors_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListSponsorsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/channels/sponsors/types/list_sponsors_request_status.py b/src/arcmira/channels/sponsors/types/list_sponsors_request_status.py new file mode 100644 index 0000000..c68b69c --- /dev/null +++ b/src/arcmira/channels/sponsors/types/list_sponsors_request_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListSponsorsRequestStatus = typing.Union[typing.Literal["active", "lapsed", "ended", "uncertain"], typing.Any] diff --git a/src/arcmira/channels/types/__init__.py b/src/arcmira/channels/types/__init__.py new file mode 100644 index 0000000..6460332 --- /dev/null +++ b/src/arcmira/channels/types/__init__.py @@ -0,0 +1,34 @@ +# 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 .coverage_channels_request_src import CoverageChannelsRequestSrc +_dynamic_imports: typing.Dict[str, str] = {"CoverageChannelsRequestSrc": ".coverage_channels_request_src"} + + +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__ = ["CoverageChannelsRequestSrc"] diff --git a/src/arcmira/channels/types/coverage_channels_request_src.py b/src/arcmira/channels/types/coverage_channels_request_src.py new file mode 100644 index 0000000..abfa9da --- /dev/null +++ b/src/arcmira/channels/types/coverage_channels_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CoverageChannelsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/channels/videos/__init__.py b/src/arcmira/channels/videos/__init__.py new file mode 100644 index 0000000..a17a5a3 --- /dev/null +++ b/src/arcmira/channels/videos/__init__.py @@ -0,0 +1,34 @@ +# 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 ListVideosRequestSrc +_dynamic_imports: typing.Dict[str, str] = {"ListVideosRequestSrc": ".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__ = ["ListVideosRequestSrc"] diff --git a/src/arcmira/channels/videos/client.py b/src/arcmira/channels/videos/client.py new file mode 100644 index 0000000..88434c3 --- /dev/null +++ b/src/arcmira/channels/videos/client.py @@ -0,0 +1,188 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.channel_videos_response import ChannelVideosResponse +from ...types.channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem +from .raw_client import AsyncRawVideosClient, RawVideosClient +from .types.list_videos_request_src import ListVideosRequestSrc + + +class VideosClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawVideosClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawVideosClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawVideosClient + """ + return self._raw_client + + def list( + self, + channel_id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + src: typing.Optional[ListVideosRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: + """ + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + limit : typing.Optional[int] + Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. + + cursor : typing.Optional[str] + Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + + published_after : typing.Optional[str] + ISO date. Only videos published on or after this day. + + published_before : typing.Optional[str] + ISO date. Only videos published before this day. + + src : typing.Optional[ListVideosRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.channels.videos.list( + channel_id="channel_id", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + channel_id, + limit=limit, + cursor=cursor, + published_after=published_after, + published_before=published_before, + src=src, + request_options=request_options, + ) + + +class AsyncVideosClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawVideosClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawVideosClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawVideosClient + """ + return self._raw_client + + async def list( + self, + channel_id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + src: typing.Optional[ListVideosRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: + """ + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + limit : typing.Optional[int] + Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. + + cursor : typing.Optional[str] + Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + + published_after : typing.Optional[str] + ISO date. Only videos published on or after this day. + + published_before : typing.Optional[str] + ISO date. Only videos published before this day. + + src : typing.Optional[ListVideosRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.channels.videos.list( + channel_id="channel_id", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + channel_id, + limit=limit, + cursor=cursor, + published_after=published_after, + published_before=published_before, + src=src, + request_options=request_options, + ) diff --git a/src/arcmira/channels/videos/raw_client.py b/src/arcmira/channels/videos/raw_client.py new file mode 100644 index 0000000..6b3388e --- /dev/null +++ b/src/arcmira/channels/videos/raw_client.py @@ -0,0 +1,361 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.channel_videos_response import ChannelVideosResponse +from ...types.channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem +from ...types.error import Error +from .types.list_videos_request_src import ListVideosRequestSrc +from pydantic import ValidationError + + +class RawVideosClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + channel_id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + src: typing.Optional[ListVideosRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: + """ + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + limit : typing.Optional[int] + Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. + + cursor : typing.Optional[str] + Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + + published_after : typing.Optional[str] + ISO date. Only videos published on or after this day. + + published_before : typing.Optional[str] + ISO date. Only videos published before this day. + + src : typing.Optional[ListVideosRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(channel_id)}/videos", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "published_after": published_after, + "published_before": published_before, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + ChannelVideosResponse, + parse_obj_as( + type_=ChannelVideosResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.episodes + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + channel_id, + limit=limit, + cursor=_parsed_next, + published_after=published_after, + published_before=published_before, + src=src, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawVideosClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + channel_id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + src: typing.Optional[ListVideosRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: + """ + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_id : str + YouTube channel id, the UC... form. + + limit : typing.Optional[int] + Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. + + cursor : typing.Optional[str] + Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + + published_after : typing.Optional[str] + ISO date. Only videos published on or after this day. + + published_before : typing.Optional[str] + ISO date. Only videos published before this day. + + src : typing.Optional[ListVideosRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/channels/{encode_path_param(channel_id)}/videos", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "published_after": published_after, + "published_before": published_before, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + ChannelVideosResponse, + parse_obj_as( + type_=ChannelVideosResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.episodes + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + channel_id, + limit=limit, + cursor=_parsed_next, + published_after=published_after, + published_before=published_before, + src=src, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/channels/videos/types/__init__.py b/src/arcmira/channels/videos/types/__init__.py new file mode 100644 index 0000000..4f8b6aa --- /dev/null +++ b/src/arcmira/channels/videos/types/__init__.py @@ -0,0 +1,34 @@ +# 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 .list_videos_request_src import ListVideosRequestSrc +_dynamic_imports: typing.Dict[str, str] = {"ListVideosRequestSrc": ".list_videos_request_src"} + + +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__ = ["ListVideosRequestSrc"] diff --git a/src/arcmira/channels/videos/types/list_videos_request_src.py b/src/arcmira/channels/videos/types/list_videos_request_src.py new file mode 100644 index 0000000..f8ddbb2 --- /dev/null +++ b/src/arcmira/channels/videos/types/list_videos_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListVideosRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/client.py b/src/arcmira/client.py new file mode 100644 index 0000000..5aec0c2 --- /dev/null +++ b/src/arcmira/client.py @@ -0,0 +1,654 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import os +import typing + +import httpx +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.logging import LogConfig, Logger +from .core.request_options import RequestOptions +from .environment import ArcmiraEnvironment +from .raw_client import AsyncRawArcmira, RawArcmira +from .types.search_request_type import SearchRequestType +from .types.search_resolve_response import SearchResolveResponse + +if typing.TYPE_CHECKING: + from .channels.client import AsyncChannelsClient, ChannelsClient + from .corrections.client import AsyncCorrectionsClient, CorrectionsClient + from .entities.client import AsyncEntitiesClient, EntitiesClient + from .feedback.client import AsyncFeedbackClient, FeedbackClient + from .health.client import AsyncHealthClient, HealthClient + from .me.client import AsyncMeClient, MeClient + from .mentions.client import AsyncMentionsClient, MentionsClient + from .meta.client import AsyncMetaClient, MetaClient + from .monitors.client import AsyncMonitorsClient, MonitorsClient + from .organizations.client import AsyncOrganizationsClient, OrganizationsClient + from .people.client import AsyncPeopleClient, PeopleClient + from .products.client import AsyncProductsClient, ProductsClient + from .recommendations.client import AsyncRecommendationsClient, RecommendationsClient + from .team.client import AsyncTeamClient, TeamClient + from .topics.client import AsyncTopicsClient, TopicsClient + from .trackers.client import AsyncTrackersClient, TrackersClient + from .transcripts.client import AsyncTranscriptsClient, TranscriptsClient + + +class Arcmira: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : ArcmiraEnvironment + The environment to use for requests from the client. from .environment import ArcmiraEnvironment + + + + Defaults to ArcmiraEnvironment.DEFAULT + + + + api_key : typing.Optional[typing.Union[str, typing.Callable[[], str]]] + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + max_retries : typing.Optional[int] + The default maximum number of retries for failed requests. Defaults to 2. Per-request `max_retries` in `request_options` takes precedence over this value. + + stream_reconnection_enabled : typing.Optional[bool] + Whether to automatically reconnect on stream disconnection for resumable streaming endpoints. Defaults to True. Per-request `stream_reconnection_enabled` in `request_options` takes precedence over this value. + + max_stream_reconnection_attempts : typing.Optional[int] + The maximum number of reconnection attempts for resumable streaming endpoints. Defaults to no limit. Per-request `max_stream_reconnection_attempts` in `request_options` takes precedence over this value. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: ArcmiraEnvironment = ArcmiraEnvironment.DEFAULT, + api_key: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = os.getenv("ARCMIRA_API_KEY"), + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + _defaulted_max_retries = max_retries if max_retries is not None else 2 + self._client_wrapper = SyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + api_key=api_key, + headers=headers, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, + logging=logging, + ) + self._raw_client = RawArcmira(client_wrapper=self._client_wrapper) + self._health: typing.Optional[HealthClient] = None + self._meta: typing.Optional[MetaClient] = None + self._me: typing.Optional[MeClient] = None + self._entities: typing.Optional[EntitiesClient] = None + self._mentions: typing.Optional[MentionsClient] = None + self._recommendations: typing.Optional[RecommendationsClient] = None + self._feedback: typing.Optional[FeedbackClient] = None + self._transcripts: typing.Optional[TranscriptsClient] = None + self._channels: typing.Optional[ChannelsClient] = None + self._people: typing.Optional[PeopleClient] = None + self._topics: typing.Optional[TopicsClient] = None + self._organizations: typing.Optional[OrganizationsClient] = None + self._products: typing.Optional[ProductsClient] = None + self._monitors: typing.Optional[MonitorsClient] = None + self._trackers: typing.Optional[TrackersClient] = None + self._team: typing.Optional[TeamClient] = None + self._corrections: typing.Optional[CorrectionsClient] = None + + @property + def with_raw_response(self) -> RawArcmira: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawArcmira + """ + return self._raw_client + + def search( + self, + *, + q: str, + type: typing.Optional[SearchRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SearchResolveResponse: + """ + Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. + + Parameters + ---------- + q : str + Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. + + type : typing.Optional[SearchRequestType] + Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SearchResolveResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.search( + q="q", + ) + """ + _response = self._raw_client.search(q=q, type=type, request_options=request_options) + return _response.data + + @property + def health(self): + if self._health is None: + from .health.client import HealthClient # noqa: E402 + + self._health = HealthClient(client_wrapper=self._client_wrapper) + return self._health + + @property + def meta(self): + if self._meta is None: + from .meta.client import MetaClient # noqa: E402 + + self._meta = MetaClient(client_wrapper=self._client_wrapper) + return self._meta + + @property + def me(self): + if self._me is None: + from .me.client import MeClient # noqa: E402 + + self._me = MeClient(client_wrapper=self._client_wrapper) + return self._me + + @property + def entities(self): + if self._entities is None: + from .entities.client import EntitiesClient # noqa: E402 + + self._entities = EntitiesClient(client_wrapper=self._client_wrapper) + return self._entities + + @property + def mentions(self): + if self._mentions is None: + from .mentions.client import MentionsClient # noqa: E402 + + self._mentions = MentionsClient(client_wrapper=self._client_wrapper) + return self._mentions + + @property + def recommendations(self): + if self._recommendations is None: + from .recommendations.client import RecommendationsClient # noqa: E402 + + self._recommendations = RecommendationsClient(client_wrapper=self._client_wrapper) + return self._recommendations + + @property + def feedback(self): + if self._feedback is None: + from .feedback.client import FeedbackClient # noqa: E402 + + self._feedback = FeedbackClient(client_wrapper=self._client_wrapper) + return self._feedback + + @property + def transcripts(self): + if self._transcripts is None: + from .transcripts.client import TranscriptsClient # noqa: E402 + + self._transcripts = TranscriptsClient(client_wrapper=self._client_wrapper) + return self._transcripts + + @property + def channels(self): + if self._channels is None: + from .channels.client import ChannelsClient # noqa: E402 + + self._channels = ChannelsClient(client_wrapper=self._client_wrapper) + return self._channels + + @property + def people(self): + if self._people is None: + from .people.client import PeopleClient # noqa: E402 + + self._people = PeopleClient(client_wrapper=self._client_wrapper) + return self._people + + @property + def topics(self): + if self._topics is None: + from .topics.client import TopicsClient # noqa: E402 + + self._topics = TopicsClient(client_wrapper=self._client_wrapper) + return self._topics + + @property + def organizations(self): + if self._organizations is None: + from .organizations.client import OrganizationsClient # noqa: E402 + + self._organizations = OrganizationsClient(client_wrapper=self._client_wrapper) + return self._organizations + + @property + def products(self): + if self._products is None: + from .products.client import ProductsClient # noqa: E402 + + self._products = ProductsClient(client_wrapper=self._client_wrapper) + return self._products + + @property + def monitors(self): + if self._monitors is None: + from .monitors.client import MonitorsClient # noqa: E402 + + self._monitors = MonitorsClient(client_wrapper=self._client_wrapper) + return self._monitors + + @property + def trackers(self): + if self._trackers is None: + from .trackers.client import TrackersClient # noqa: E402 + + self._trackers = TrackersClient(client_wrapper=self._client_wrapper) + return self._trackers + + @property + def team(self): + if self._team is None: + from .team.client import TeamClient # noqa: E402 + + self._team = TeamClient(client_wrapper=self._client_wrapper) + return self._team + + @property + def corrections(self): + if self._corrections is None: + from .corrections.client import CorrectionsClient # noqa: E402 + + self._corrections = CorrectionsClient(client_wrapper=self._client_wrapper) + return self._corrections + + +def _make_default_async_client( + timeout: typing.Optional[float], + follow_redirects: typing.Optional[bool], +) -> httpx.AsyncClient: + try: + import httpx_aiohttp # type: ignore[import-not-found] + except ImportError: + pass + else: + if follow_redirects is not None: + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) + + if follow_redirects is not None: + return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx.AsyncClient(timeout=timeout) + + +class AsyncArcmira: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : ArcmiraEnvironment + The environment to use for requests from the client. from .environment import ArcmiraEnvironment + + + + Defaults to ArcmiraEnvironment.DEFAULT + + + + api_key : typing.Optional[typing.Union[str, typing.Callable[[], str]]] + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + async_token : typing.Optional[typing.Callable[[], typing.Awaitable[str]]] + An async callable that returns a bearer token. Use this when token acquisition involves async I/O (e.g., refreshing tokens via an async HTTP client). When provided, this is used instead of the synchronous token for async requests. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + max_retries : typing.Optional[int] + The default maximum number of retries for failed requests. Defaults to 2. Per-request `max_retries` in `request_options` takes precedence over this value. + + stream_reconnection_enabled : typing.Optional[bool] + Whether to automatically reconnect on stream disconnection for resumable streaming endpoints. Defaults to True. Per-request `stream_reconnection_enabled` in `request_options` takes precedence over this value. + + max_stream_reconnection_attempts : typing.Optional[int] + The maximum number of reconnection attempts for resumable streaming endpoints. Defaults to no limit. Per-request `max_stream_reconnection_attempts` in `request_options` takes precedence over this value. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: ArcmiraEnvironment = ArcmiraEnvironment.DEFAULT, + api_key: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = os.getenv("ARCMIRA_API_KEY"), + headers: typing.Optional[typing.Dict[str, str]] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, + timeout: typing.Optional[float] = None, + max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + _defaulted_max_retries = max_retries if max_retries is not None else 2 + self._client_wrapper = AsyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + api_key=api_key, + headers=headers, + async_token=async_token, + httpx_client=httpx_client + if httpx_client is not None + else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), + timeout=_defaulted_timeout, + max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, + logging=logging, + ) + self._raw_client = AsyncRawArcmira(client_wrapper=self._client_wrapper) + self._health: typing.Optional[AsyncHealthClient] = None + self._meta: typing.Optional[AsyncMetaClient] = None + self._me: typing.Optional[AsyncMeClient] = None + self._entities: typing.Optional[AsyncEntitiesClient] = None + self._mentions: typing.Optional[AsyncMentionsClient] = None + self._recommendations: typing.Optional[AsyncRecommendationsClient] = None + self._feedback: typing.Optional[AsyncFeedbackClient] = None + self._transcripts: typing.Optional[AsyncTranscriptsClient] = None + self._channels: typing.Optional[AsyncChannelsClient] = None + self._people: typing.Optional[AsyncPeopleClient] = None + self._topics: typing.Optional[AsyncTopicsClient] = None + self._organizations: typing.Optional[AsyncOrganizationsClient] = None + self._products: typing.Optional[AsyncProductsClient] = None + self._monitors: typing.Optional[AsyncMonitorsClient] = None + self._trackers: typing.Optional[AsyncTrackersClient] = None + self._team: typing.Optional[AsyncTeamClient] = None + self._corrections: typing.Optional[AsyncCorrectionsClient] = None + + @property + def with_raw_response(self) -> AsyncRawArcmira: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawArcmira + """ + return self._raw_client + + async def search( + self, + *, + q: str, + type: typing.Optional[SearchRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SearchResolveResponse: + """ + Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. + + Parameters + ---------- + q : str + Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. + + type : typing.Optional[SearchRequestType] + Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SearchResolveResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.search( + q="q", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.search(q=q, type=type, request_options=request_options) + return _response.data + + @property + def health(self): + if self._health is None: + from .health.client import AsyncHealthClient # noqa: E402 + + self._health = AsyncHealthClient(client_wrapper=self._client_wrapper) + return self._health + + @property + def meta(self): + if self._meta is None: + from .meta.client import AsyncMetaClient # noqa: E402 + + self._meta = AsyncMetaClient(client_wrapper=self._client_wrapper) + return self._meta + + @property + def me(self): + if self._me is None: + from .me.client import AsyncMeClient # noqa: E402 + + self._me = AsyncMeClient(client_wrapper=self._client_wrapper) + return self._me + + @property + def entities(self): + if self._entities is None: + from .entities.client import AsyncEntitiesClient # noqa: E402 + + self._entities = AsyncEntitiesClient(client_wrapper=self._client_wrapper) + return self._entities + + @property + def mentions(self): + if self._mentions is None: + from .mentions.client import AsyncMentionsClient # noqa: E402 + + self._mentions = AsyncMentionsClient(client_wrapper=self._client_wrapper) + return self._mentions + + @property + def recommendations(self): + if self._recommendations is None: + from .recommendations.client import AsyncRecommendationsClient # noqa: E402 + + self._recommendations = AsyncRecommendationsClient(client_wrapper=self._client_wrapper) + return self._recommendations + + @property + def feedback(self): + if self._feedback is None: + from .feedback.client import AsyncFeedbackClient # noqa: E402 + + self._feedback = AsyncFeedbackClient(client_wrapper=self._client_wrapper) + return self._feedback + + @property + def transcripts(self): + if self._transcripts is None: + from .transcripts.client import AsyncTranscriptsClient # noqa: E402 + + self._transcripts = AsyncTranscriptsClient(client_wrapper=self._client_wrapper) + return self._transcripts + + @property + def channels(self): + if self._channels is None: + from .channels.client import AsyncChannelsClient # noqa: E402 + + self._channels = AsyncChannelsClient(client_wrapper=self._client_wrapper) + return self._channels + + @property + def people(self): + if self._people is None: + from .people.client import AsyncPeopleClient # noqa: E402 + + self._people = AsyncPeopleClient(client_wrapper=self._client_wrapper) + return self._people + + @property + def topics(self): + if self._topics is None: + from .topics.client import AsyncTopicsClient # noqa: E402 + + self._topics = AsyncTopicsClient(client_wrapper=self._client_wrapper) + return self._topics + + @property + def organizations(self): + if self._organizations is None: + from .organizations.client import AsyncOrganizationsClient # noqa: E402 + + self._organizations = AsyncOrganizationsClient(client_wrapper=self._client_wrapper) + return self._organizations + + @property + def products(self): + if self._products is None: + from .products.client import AsyncProductsClient # noqa: E402 + + self._products = AsyncProductsClient(client_wrapper=self._client_wrapper) + return self._products + + @property + def monitors(self): + if self._monitors is None: + from .monitors.client import AsyncMonitorsClient # noqa: E402 + + self._monitors = AsyncMonitorsClient(client_wrapper=self._client_wrapper) + return self._monitors + + @property + def trackers(self): + if self._trackers is None: + from .trackers.client import AsyncTrackersClient # noqa: E402 + + self._trackers = AsyncTrackersClient(client_wrapper=self._client_wrapper) + return self._trackers + + @property + def team(self): + if self._team is None: + from .team.client import AsyncTeamClient # noqa: E402 + + self._team = AsyncTeamClient(client_wrapper=self._client_wrapper) + return self._team + + @property + def corrections(self): + if self._corrections is None: + from .corrections.client import AsyncCorrectionsClient # noqa: E402 + + self._corrections = AsyncCorrectionsClient(client_wrapper=self._client_wrapper) + return self._corrections + + +def _get_base_url(*, base_url: typing.Optional[str] = None, environment: ArcmiraEnvironment) -> str: + if base_url is not None: + return base_url + elif environment is not None: + return environment.value + else: + raise Exception("Please pass in either base_url or environment to construct the client") diff --git a/src/arcmira/core/__init__.py b/src/arcmira/core/__init__.py new file mode 100644 index 0000000..e2be580 --- /dev/null +++ b/src/arcmira/core/__init__.py @@ -0,0 +1,132 @@ +# 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 .api_error import ApiError + from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper + 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 encode_path_param, jsonable_encoder + from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger + from .pagination import AsyncPager, SyncPager + from .parse_error import ParsingError + from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, + ) + from .query_encoder import encode_query + from .remove_none_from_dict import remove_none_from_dict + from .request_options import RequestOptions + from .serialization import FieldMetadata, convert_and_respect_annotation_metadata +_dynamic_imports: typing.Dict[str, str] = { + "ApiError": ".api_error", + "AsyncClientWrapper": ".client_wrapper", + "AsyncHttpClient": ".http_client", + "AsyncHttpResponse": ".http_response", + "AsyncPager": ".pagination", + "BaseClientWrapper": ".client_wrapper", + "ConsoleLogger": ".logging", + "FieldMetadata": ".serialization", + "File": ".file", + "HttpClient": ".http_client", + "HttpResponse": ".http_response", + "ILogger": ".logging", + "IS_PYDANTIC_V2": ".pydantic_utilities", + "LogConfig": ".logging", + "LogLevel": ".logging", + "Logger": ".logging", + "ParsingError": ".parse_error", + "RequestOptions": ".request_options", + "Rfc2822DateTime": ".datetime_utils", + "SyncClientWrapper": ".client_wrapper", + "SyncPager": ".pagination", + "UniversalBaseModel": ".pydantic_utilities", + "UniversalRootModel": ".pydantic_utilities", + "convert_and_respect_annotation_metadata": ".serialization", + "convert_file_dict_to_httpx_tuples": ".file", + "create_logger": ".logging", + "encode_path_param": ".jsonable_encoder", + "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", + "universal_root_validator": ".pydantic_utilities", + "update_forward_refs": ".pydantic_utilities", + "with_content_type": ".file", +} + + +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__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "AsyncHttpResponse", + "AsyncPager", + "BaseClientWrapper", + "ConsoleLogger", + "FieldMetadata", + "File", + "HttpClient", + "HttpResponse", + "ILogger", + "IS_PYDANTIC_V2", + "LogConfig", + "LogLevel", + "Logger", + "ParsingError", + "RequestOptions", + "Rfc2822DateTime", + "SyncClientWrapper", + "SyncPager", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "create_logger", + "encode_path_param", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "parse_rfc2822_datetime", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", + "with_content_type", +] diff --git a/src/arcmira/core/api_error.py b/src/arcmira/core/api_error.py new file mode 100644 index 0000000..6f850a6 --- /dev/null +++ b/src/arcmira/core/api_error.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ApiError(Exception): + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/src/arcmira/core/client_wrapper.py b/src/arcmira/core/client_wrapper.py new file mode 100644 index 0000000..d359092 --- /dev/null +++ b/src/arcmira/core/client_wrapper.py @@ -0,0 +1,147 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .http_client import AsyncHttpClient, HttpClient +from .logging import LogConfig, Logger + + +class BaseClientWrapper: + def __init__( + self, + *, + api_key: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + max_retries: int = 2, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self._api_key = api_key + self._headers = headers + self._base_url = base_url + self._timeout = timeout + self._max_retries = max_retries + self._stream_reconnection_enabled = stream_reconnection_enabled + self._max_stream_reconnection_attempts = max_stream_reconnection_attempts + self._logging = logging + + def get_headers(self) -> typing.Dict[str, str]: + import platform + + headers: typing.Dict[str, str] = { + "User-Agent": "arcmira/0.3.0", + "X-Fern-Language": "Python", + "X-Fern-Runtime": f"python/{platform.python_version()}", + "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", + **(self.get_custom_headers() or {}), + } + api_key = self._get_api_key() + if api_key is not None: + headers["Authorization"] = f"Bearer {api_key}" + return headers + + def _get_api_key(self) -> typing.Optional[str]: + if isinstance(self._api_key, str) or self._api_key is None: + return self._api_key + else: + return self._api_key() + + def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: + return self._headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + def get_max_retries(self) -> int: + return self._max_retries + + def get_stream_reconnection_enabled(self) -> bool: + return self._stream_reconnection_enabled if self._stream_reconnection_enabled is not None else True + + def get_max_stream_reconnection_attempts(self) -> typing.Optional[int]: + return self._max_stream_reconnection_attempts + + +class SyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + api_key: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + max_retries: int = 2, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + httpx_client: httpx.Client, + ): + super().__init__( + api_key=api_key, + headers=headers, + base_url=base_url, + timeout=timeout, + max_retries=max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, + logging=logging, + ) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + base_max_retries=self.get_max_retries(), + logging_config=self._logging, + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + api_key: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + max_retries: int = 2, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, + httpx_client: httpx.AsyncClient, + ): + super().__init__( + api_key=api_key, + headers=headers, + base_url=base_url, + timeout=timeout, + max_retries=max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, + logging=logging, + ) + self._async_token = async_token + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + base_max_retries=self.get_max_retries(), + async_base_headers=self.async_get_headers, + logging_config=self._logging, + ) + + async def async_get_headers(self) -> typing.Dict[str, str]: + headers = self.get_headers() + if self._async_token is not None: + token = await self._async_token() + headers["Authorization"] = f"Bearer {token}" + return headers diff --git a/src/arcmira/core/datetime_utils.py b/src/arcmira/core/datetime_utils.py new file mode 100644 index 0000000..a12b2ad --- /dev/null +++ b/src/arcmira/core/datetime_utils.py @@ -0,0 +1,70 @@ +# 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: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/src/arcmira/core/file.py b/src/arcmira/core/file.py new file mode 100644 index 0000000..44b0d27 --- /dev/null +++ b/src/arcmira/core/file.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = Union[IO[bytes], bytes, str] +File = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[ + Optional[str], + FileContent, + Optional[str], + Mapping[str, str], + ], +] + + +def convert_file_dict_to_httpx_tuples( + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples + + +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, default_content_type) + elif len(file) == 3: + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) + elif len(file) == 4: + filename, content, file_content_type, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file + ) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, default_content_type) diff --git a/src/arcmira/core/force_multipart.py b/src/arcmira/core/force_multipart.py new file mode 100644 index 0000000..5440913 --- /dev/null +++ b/src/arcmira/core/force_multipart.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict + + +class ForceMultipartDict(Dict[str, Any]): + """ + A dictionary subclass that always evaluates to True in boolean contexts. + + This is used to force multipart/form-data encoding in HTTP requests even when + the dictionary is empty, which would normally evaluate to False. + """ + + def __bool__(self) -> bool: + return True + + +FORCE_MULTIPART = ForceMultipartDict() diff --git a/src/arcmira/core/http_client.py b/src/arcmira/core/http_client.py new file mode 100644 index 0000000..3e5a786 --- /dev/null +++ b/src/arcmira/core/http_client.py @@ -0,0 +1,940 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import re +import socket +import time +import typing +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx +from .file import File, convert_file_dict_to_httpx_tuples +from .force_multipart import FORCE_MULTIPART +from .jsonable_encoder import jsonable_encoder +from .logging import LogConfig, Logger, create_logger +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict +from .request_options import RequestOptions +from httpx._types import RequestFiles + +INITIAL_RETRY_DELAY_SECONDS = 1.0 +MAX_RETRY_DELAY_SECONDS = 60.0 +JITTER_FACTOR = 0.2 # 20% random jitter + + +def get_keepalive_socket_options( + idle: int = 60, + intvl: int = 30, + cnt: int = 5, +) -> typing.List[typing.Tuple[int, int, int]]: + """ + Build TCP keepalive socket options for the current platform. + + Keepalive probes keep otherwise-idle connections alive so that long, + non-streaming requests survive idle-connection reaping by a firewall, + load balancer, or NAT. The available socket constants are OS-dependent, + so each option is guarded and only emitted when the platform defines it: + + - ``SO_KEEPALIVE`` is portable (Linux/macOS/Windows). + - The idle-before-first-probe knob is ``TCP_KEEPIDLE`` on Linux and modern + Windows, but ``TCP_KEEPALIVE`` on macOS. + - ``TCP_KEEPINTVL`` / ``TCP_KEEPCNT`` exist on Linux/macOS/modern Windows. + + Passing these tuples to ``httpx.HTTPTransport(socket_options=...)`` / + ``httpx.AsyncHTTPTransport(socket_options=...)`` applies them to every + connection the transport opens. + """ + opts: typing.List[typing.Tuple[int, int, int]] = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)] + idle_const = getattr(socket, "TCP_KEEPIDLE", None) or getattr(socket, "TCP_KEEPALIVE", None) + if idle_const: + opts.append((socket.IPPROTO_TCP, idle_const, idle)) + if hasattr(socket, "TCP_KEEPINTVL"): + opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, intvl)) + if hasattr(socket, "TCP_KEEPCNT"): + opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, cnt)) + return opts + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _add_positive_jitter(delay: float) -> float: + """Add positive jitter (0-20%) to prevent thundering herd.""" + jitter_multiplier = 1 + random() * JITTER_FACTOR + return delay * jitter_multiplier + + +def _add_symmetric_jitter(delay: float) -> float: + """Add symmetric jitter (±10%) for exponential backoff.""" + jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR + return delay * jitter_multiplier + + +def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + Parse the X-RateLimit-Reset header (Unix timestamp in seconds). + Returns seconds to wait, or None if header is missing/invalid. + """ + reset_time_str = response_headers.get("x-ratelimit-reset") + if reset_time_str is None: + return None + + try: + reset_time = int(reset_time_str) + delay = reset_time - time.time() + if delay > 0: + return delay + except (ValueError, TypeError): + pass + + return None + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # 1. Check Retry-After header first + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after > 0: + return min(retry_after, MAX_RETRY_DELAY_SECONDS) + + # 2. Check X-RateLimit-Reset header (with positive jitter) + ratelimit_reset = _parse_x_ratelimit_reset(response.headers) + if ratelimit_reset is not None: + return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) + + # 3. Fall back to exponential backoff (with symmetric jitter) + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _retry_timeout_from_retries(retries: int) -> float: + """Determine retry timeout using exponential backoff when no response is available.""" + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _should_retry(response: httpx.Response) -> bool: + return response.status_code >= 500 or response.status_code in [429, 408, 409] + + +_SENSITIVE_HEADERS = frozenset( + { + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "cookie", + "set-cookie", + "proxy-authorization", + "proxy-authenticate", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token", + } +) + + +def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]: + return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()} + + +def _build_url(base_url: str, path: typing.Optional[str]) -> str: + """ + Build a full URL by joining a base URL with a path. + + This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs) + by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly + strip path components when the path starts with '/'. + + Example: + >>> _build_url("https://cloud.example.com/org/tenant/api", "/users") + 'https://cloud.example.com/org/tenant/api/users' + + Args: + base_url: The base URL, which may contain path prefixes. + path: The path to append. Can be None or empty string. + + Returns: + The full URL with base_url and path properly joined. + """ + if not path: + return base_url + return f"{base_url.rstrip('/')}/{path.lstrip('/')}" + + +def _maybe_filter_none_from_multipart_data( + data: typing.Optional[typing.Any], + request_files: typing.Optional[RequestFiles], + force_multipart: typing.Optional[bool], +) -> typing.Optional[typing.Any]: + """ + Filter None values from data body for multipart/form requests. + This prevents httpx from converting None to empty strings in multipart encoding. + Only applies when files are present or force_multipart is True. + """ + if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart): + return remove_none_from_dict(data) + return data + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], + omit: typing.Optional[typing.Any], +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], + optional_body: bool = False, +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + # A whole body left at the sentinel was never passed by the caller, so it is absent + # rather than empty: the request carries no content and no `Content-Type`. + if omit is not None: + if json is omit: + json = None + if data is omit: + data = None + + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + has_additional_body_parameters = bool( + request_options is not None and request_options.get("additional_body_parameters") + ) + + # Only collapse empty dict to None when the body was not explicitly provided + # and there are no additional body parameters. This preserves explicit empty + # bodies (e.g., when an endpoint has a request body type but all fields are optional). + # `optional_body` marks an endpoint whose body the API does not require, where a body + # that ends up empty means the caller passed none of its properties, so the request is + # sent with no content and no `Content-Type`. + if json_body == {} and (json is None or optional_body) and not has_additional_body_parameters: + json_body = None + if data_body == {} and (data is None or optional_body) and not has_additional_body_parameters: + data_body = None + + return json_body, data_body + + +def drop_content_type_without_body( + headers: typing.Dict[str, typing.Any], + *, + json_body: typing.Optional[typing.Any], + data_body: typing.Optional[typing.Any], + optional_body: bool, +) -> typing.Dict[str, typing.Any]: + """Strip ``Content-Type`` from a request that carries no body. + + ``get_request_body`` drops the body of an ``optional_body`` endpoint when the caller + supplied none of it, but the endpoint still passes the content type it would have used. + A request that sends nothing must not advertise a media type, so a server that branches + on the header sees a bodyless call for what it is. + """ + if not optional_body or json_body is not None or data_body is not None: + return headers + return {key: value for key, value in headers.items() if key.lower() != "content-type"} + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + _timeout = ( + request_options.get("timeout") + if request_options is not None and request_options.get("timeout") is not None + else request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT + + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + time.sleep(_retry_timeout_from_retries(retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, + force_multipart: typing.Optional[bool] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + _timeout = ( + request_options.get("timeout") + if request_options is not None and request_options.get("timeout") is not None + else request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.async_base_headers = async_base_headers + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + async def _get_headers(self) -> typing.Dict[str, str]: + if self.async_base_headers is not None: + return await self.async_base_headers() + return self.base_headers() + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + _timeout = ( + request_options.get("timeout") + if request_options is not None and request_options.get("timeout") is not None + else request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = await self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + await asyncio.sleep(_retry_timeout_from_retries(retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, + force_multipart: typing.Optional[bool] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + _timeout = ( + request_options.get("timeout") + if request_options is not None and request_options.get("timeout") is not None + else request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + async with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream diff --git a/src/arcmira/core/http_response.py b/src/arcmira/core/http_response.py new file mode 100644 index 0000000..9aa1e18 --- /dev/null +++ b/src/arcmira/core/http_response.py @@ -0,0 +1,63 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Dict, Generic, TypeVar + +import httpx + +# Generic to represent the underlying type of the data wrapped by the HTTP response. +T = TypeVar("T") + + +class BaseHttpResponse: + """Minimalist HTTP response wrapper that exposes response headers and status code.""" + + _response: httpx.Response + + def __init__(self, response: httpx.Response): + self._response = response + + @property + def headers(self) -> Dict[str, str]: + return dict(self._response.headers) + + @property + def status_code(self) -> int: + return self._response.status_code + + @property + def response(self) -> httpx.Response: + return self._response + + +class HttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + def close(self) -> None: + self._response.close() + + +class AsyncHttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + async def close(self) -> None: + await self._response.aclose() diff --git a/src/arcmira/core/http_sse/__init__.py b/src/arcmira/core/http_sse/__init__.py new file mode 100644 index 0000000..730e5a3 --- /dev/null +++ b/src/arcmira/core/http_sse/__init__.py @@ -0,0 +1,42 @@ +# 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 ._api import EventSource, aconnect_sse, connect_sse + from ._exceptions import SSEError + from ._models import ServerSentEvent +_dynamic_imports: typing.Dict[str, str] = { + "EventSource": "._api", + "SSEError": "._exceptions", + "ServerSentEvent": "._models", + "aconnect_sse": "._api", + "connect_sse": "._api", +} + + +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__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/src/arcmira/core/http_sse/_api.py b/src/arcmira/core/http_sse/_api.py new file mode 100644 index 0000000..9ca5602 --- /dev/null +++ b/src/arcmira/core/http_sse/_api.py @@ -0,0 +1,455 @@ +# This file was auto-generated by Fern from our API Definition. + +import codecs +import re +import time +from contextlib import asynccontextmanager, contextmanager +from typing import ( + Any, + AsyncContextManager, + AsyncGenerator, + AsyncIterator, + Callable, + ContextManager, + Iterator, + Optional, +) + +import anyio +import httpx +from ._decoders import SSEDecoder +from ._exceptions import SSEError +from ._models import ServerSentEvent + +MAX_LINE_SIZE: int = 1_048_576 # 1 MiB + +# Reconnection defaults, mirroring the TypeScript SDK's Stream implementation. +DEFAULT_MAX_RECONNECTION_ATTEMPTS: int = 5 +DEFAULT_RECONNECT_DELAY_MS: int = 1_000 +MAX_RECONNECT_DELAY_MS: int = 30_000 + + +# A reconnect callback re-issues the original request (with a ``Last-Event-ID`` +# header set to the supplied event id) and returns a *context manager* yielding +# a fresh streaming ``httpx.Response``. Sync clients supply a sync context +# manager; async clients supply an async one. +class EventSource: + def __init__( + self, + response: httpx.Response, + *, + resumable: bool = False, + stream_reconnection_enabled: bool = True, + max_stream_reconnection_attempts: Optional[int] = None, + stream_terminator: Optional[str] = None, + reconnect: Optional[Callable[[str], Any]] = None, + ) -> None: + self._response = response + self._resumable = resumable + self._stream_reconnection_enabled = stream_reconnection_enabled + self._max_stream_reconnection_attempts = max_stream_reconnection_attempts + self._stream_terminator = stream_terminator + self._reconnect = reconnect + + @staticmethod + def _is_event_stream(response: httpx.Response) -> bool: + content_type = response.headers.get("content-type", "").partition(";")[0] + return "text/event-stream" in content_type + + def _check_content_type(self) -> None: + if not self._is_event_stream(self._response): + content_type = self._response.headers.get("content-type", "").partition(";")[0] + raise SSEError( + f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" + ) + + def _is_reconnect_response_usable(self, response: httpx.Response) -> bool: + """Whether a reconnected response can be resumed as an SSE stream. + + ``httpx.stream`` does not raise on non-success status, so a resume that + returns an error page (e.g. ``200 text/html`` or a ``500`` body) would + otherwise be parsed as SSE and yield garbage/zero events. Such a + response is treated as a failed attempt (back off and retry) instead. + """ + return response.status_code < 400 and self._is_event_stream(response) + + def _get_charset(self, response: Optional[httpx.Response] = None) -> str: + """Extract charset from Content-Type header, fallback to UTF-8.""" + resolved = response if response is not None else self._response + content_type = resolved.headers.get("content-type", "") + + # Parse charset parameter using regex + charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) + if charset_match: + charset = charset_match.group(1).strip("\"'") + # Validate that it's a known encoding + try: + # Test if the charset is valid by trying to encode/decode + "test".encode(charset).decode(charset) + return charset + except (LookupError, UnicodeError): + # If charset is invalid, fall back to UTF-8 + pass + + # Default to UTF-8 if no charset specified or invalid charset + return "utf-8" + + @property + def response(self) -> httpx.Response: + return self._response + + @staticmethod + def _normalize_sse_line_endings(buf: str) -> str: + """Normalize line endings per the SSE spec (\\r\\n → \\n, bare \\r → \\n). + + A trailing \\r is preserved because it may pair with a leading \\n in + the next chunk to form a single \\r\\n terminator. + """ + buf = buf.replace("\r\n", "\n") + if buf.endswith("\r"): + return buf[:-1].replace("\r", "\n") + "\r" + return buf.replace("\r", "\n") + + def _new_text_decoder(self, response: Optional[httpx.Response] = None) -> "codecs.IncrementalDecoder": + return codecs.getincrementaldecoder(self._get_charset(response))(errors="replace") + + def _reconnect_applicable(self) -> bool: + """Whether reconnection is configured for this stream at all. + + This is the terminator-gating half of the reconnect decision, kept + separate from :meth:`_should_reconnect` (which additionally requires a + last *dispatched* id and an unexhausted attempt budget). The split lets + a mid-stream transport error terminate consistently: + - a stream that can never reconnect (non-resumable, no terminator, + disabled, or no callback) must re-raise the error to the caller, so a + truncated stream is not mistaken for a clean completion; + - a resumable stream that has merely run out of attempts (or has no id + to resume from) ends cleanly — the same way an exhausted empty/error + -body resume already does, matching the TypeScript ``return``. + """ + return ( + self._resumable + and self._stream_terminator is not None + and self._stream_reconnection_enabled + and self._reconnect is not None + ) + + def _should_reconnect(self, last_dispatched_id: Optional[str], reconnect_attempts: int) -> bool: + """Decide whether a prematurely-ended stream should be reconnected. + + Mirrors the TypeScript ``shouldReconnect`` gating: + - only resumable SSE endpoints with a configured terminator, reconnect + enabled, and a reconnect callback are eligible (see + :meth:`_reconnect_applicable`); + - a last *dispatched* event id must exist to resume from; + - the consecutive-failed-attempt cap must not be exceeded. + """ + if not self._reconnect_applicable(): + return False + if not last_dispatched_id: + return False + max_attempts = ( + self._max_stream_reconnection_attempts + if self._max_stream_reconnection_attempts is not None + else DEFAULT_MAX_RECONNECTION_ATTEMPTS + ) + if reconnect_attempts >= max_attempts: + return False + return True + + def _reconnect_delay_seconds(self, last_retry: Optional[int]) -> float: + """Backoff before a reconnect. + + Uses the server's most recent ``retry:`` directive (milliseconds) when + present, otherwise a default of ``DEFAULT_RECONNECT_DELAY_MS``, clamped + to ``MAX_RECONNECT_DELAY_MS``. + """ + base_ms = last_retry if (last_retry is not None and last_retry > 0) else DEFAULT_RECONNECT_DELAY_MS + return min(base_ms, MAX_RECONNECT_DELAY_MS) / 1000.0 + + def _sleep_before_reconnect(self, last_retry: Optional[int]) -> None: + # ``time.sleep`` blocks the calling thread but remains interruptible by + # signals (e.g. ``KeyboardInterrupt``), which propagate out and abort + # the reconnect without issuing another request. + time.sleep(self._reconnect_delay_seconds(last_retry)) + + async def _asleep_before_reconnect(self, last_retry: Optional[int]) -> None: + # ``anyio.sleep`` is cancellation-aware: if the consumer cancels the task + # or closes the async generator mid-delay, this raises (and no further + # request is issued) instead of blocking for the whole interval. + await anyio.sleep(self._reconnect_delay_seconds(last_retry)) + + def _decode_response( + self, + response: httpx.Response, + decoder: SSEDecoder, + text_decoder: "codecs.IncrementalDecoder", + ) -> Iterator[ServerSentEvent]: + buf = "" + for chunk in response.iter_bytes(): + buf += text_decoder.decode(chunk) + buf = self._normalize_sse_line_endings(buf) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + yield from self._flush_decoder(buf, decoder, text_decoder) + + async def _adecode_response( + self, + response: httpx.Response, + decoder: SSEDecoder, + text_decoder: "codecs.IncrementalDecoder", + ) -> AsyncGenerator[ServerSentEvent, None]: + buf = "" + async for chunk in response.aiter_bytes(): + buf += text_decoder.decode(chunk) + buf = self._normalize_sse_line_endings(buf) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + for sse in self._flush_decoder(buf, decoder, text_decoder): + yield sse + + def _flush_decoder( + self, + buf: str, + decoder: SSEDecoder, + text_decoder: "codecs.IncrementalDecoder", + ) -> Iterator[ServerSentEvent]: + # Flush any remaining bytes from the incremental decoder + buf += text_decoder.decode(b"", final=True) + buf = buf.replace("\r\n", "\n").replace("\r", "\n") + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if buf.strip(): + sse = decoder.decode(buf) + if sse is not None: + yield sse + + def iter_sse(self) -> Iterator[ServerSentEvent]: + self._check_content_type() + decoder = SSEDecoder() + text_decoder = self._new_text_decoder() + + last_dispatched_id: Optional[str] = None + last_retry: Optional[int] = None + # Consecutive failed reconnection attempts. Reset to 0 whenever an event + # is successfully dispatched (reset-on-progress) — matching browser + # `EventSource` semantics: a server that emits >=1 event then drops on + # every connection can reconnect indefinitely. + reconnect_attempts = 0 + + # ``None`` means there is no live stream to read this iteration (e.g. a + # failed reconnect); the loop then re-evaluates the reconnect decision + # without re-reading an exhausted response. + response: Optional[httpx.Response] = self._response + # Context manager for a response we opened ourselves and must close. + # The initial response is owned by the caller, so it starts as None. + owned_cm: Optional[ContextManager[httpx.Response]] = None + try: + while True: + if response is not None: + events = self._decode_response(response, decoder, text_decoder) + while True: + try: + sse = next(events) + except StopIteration: + break + except SSEError: + # A protocol violation (e.g. an oversized line) is a + # genuine error, not a dropped connection; propagate it. + # Listed first because ``SSEError`` subclasses + # ``httpx.TransportError``. + raise + except httpx.TransportError: + # A transport error mid-stream (e.g. the server dropped + # the connection: ``ReadError``/``RemoteProtocolError``) + # is a premature end. Only swallow it when reconnection + # is configured for this stream; otherwise re-raise so a + # non-resumable stream still surfaces the error to the + # caller instead of looking like a clean completion. + # When reconnection is applicable but the attempt budget + # is exhausted, we ``break`` and end cleanly below — the + # same way an exhausted empty/error-body resume does, so + # give-up is consistent regardless of failure shape. + # ``next`` is used rather than ``for`` so this cannot + # swallow a ``GeneratorExit`` raised at a ``yield``. + if not self._reconnect_applicable(): + raise + break + yield sse + if sse.id: + last_dispatched_id = sse.id + if sse.retry is not None: + last_retry = sse.retry + reconnect_attempts = 0 + + if not self._should_reconnect(last_dispatched_id, reconnect_attempts): + return + reconnect_attempts += 1 + + self._sleep_before_reconnect(last_retry) + + # Close the previously-opened reconnect response before opening + # a new one so we never hold more than one extra connection. + if owned_cm is not None: + owned_cm.__exit__(None, None, None) + owned_cm = None + + assert self._reconnect is not None # guaranteed by _should_reconnect + try: + cm: ContextManager[httpx.Response] = self._reconnect(last_dispatched_id or "") + new_response = cm.__enter__() + except Exception: + # A failed reconnect consumes an attempt; back off and retry. + response = None + continue + owned_cm = cm + if new_response is None or not self._is_reconnect_response_usable(new_response): + # Null/empty body or a non-SSE/error response (e.g. 204/304, + # a 500, or an HTML error page): treat as a failed attempt. + response = None + continue + + response = new_response + # Drop any partial event left over from the dropped stream, but + # keep the last event id (per the SSE spec) and start a fresh + # incremental text decoder for the new connection. + decoder.reset_in_progress_event() + text_decoder = self._new_text_decoder(new_response) + finally: + if owned_cm is not None: + owned_cm.__exit__(None, None, None) + + async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: + self._check_content_type() + decoder = SSEDecoder() + text_decoder = self._new_text_decoder() + + last_dispatched_id: Optional[str] = None + last_retry: Optional[int] = None + reconnect_attempts = 0 + + response: Optional[httpx.Response] = self._response + owned_cm: Optional[AsyncContextManager[httpx.Response]] = None + try: + while True: + if response is not None: + events = self._adecode_response(response, decoder, text_decoder) + while True: + try: + sse = await events.__anext__() + except StopAsyncIteration: + break + except SSEError: + # A protocol violation (e.g. an oversized line) is a + # genuine error, not a dropped connection; propagate it. + # Listed first because ``SSEError`` subclasses + # ``httpx.TransportError``. + raise + except httpx.TransportError: + # A transport error mid-stream (e.g. the server dropped + # the connection: ``ReadError``/``RemoteProtocolError``) + # is a premature end. Only swallow it when reconnection + # is configured for this stream; otherwise re-raise so a + # non-resumable stream still surfaces the error to the + # caller instead of looking like a clean completion. + # When reconnection is applicable but the attempt budget + # is exhausted, we ``break`` and end cleanly below — the + # same way an exhausted empty/error-body resume does, so + # give-up is consistent regardless of failure shape. + if not self._reconnect_applicable(): + raise + break + yield sse + if sse.id: + last_dispatched_id = sse.id + if sse.retry is not None: + last_retry = sse.retry + reconnect_attempts = 0 + + if not self._should_reconnect(last_dispatched_id, reconnect_attempts): + return + reconnect_attempts += 1 + + await self._asleep_before_reconnect(last_retry) + + if owned_cm is not None: + await owned_cm.__aexit__(None, None, None) + owned_cm = None + + assert self._reconnect is not None # guaranteed by _should_reconnect + try: + cm: AsyncContextManager[httpx.Response] = self._reconnect(last_dispatched_id or "") + new_response = await cm.__aenter__() + except Exception: + response = None + continue + owned_cm = cm + if new_response is None or not self._is_reconnect_response_usable(new_response): + response = None + continue + + response = new_response + decoder.reset_in_progress_event() + text_decoder = self._new_text_decoder(new_response) + finally: + if owned_cm is not None: + # Shield the close so a cancellation delivered while reading a + # reconnected response still fully tears the connection down + # instead of leaking it until the client is closed. + with anyio.CancelScope(shield=True): + await owned_cm.__aexit__(None, None, None) + + +@contextmanager +def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +@asynccontextmanager +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: str, + **kwargs: Any, +) -> AsyncIterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + async with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) diff --git a/src/arcmira/core/http_sse/_decoders.py b/src/arcmira/core/http_sse/_decoders.py new file mode 100644 index 0000000..1f6b35e --- /dev/null +++ b/src/arcmira/core/http_sse/_decoders.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import List, Optional + +from ._models import ServerSentEvent + + +class SSEDecoder: + def __init__(self) -> None: + self._event = "" + self._data: List[str] = [] + self._last_event_id = "" + self._retry: Optional[int] = None + + def reset_in_progress_event(self) -> None: + """Discard any partially-parsed (undispatched) event. + + Used when a stream ends mid-event before reconnecting: the buffered + ``event``/``data``/``retry`` fields of the never-dispatched event must + be dropped so they do not corrupt the first event of the reconnected + stream. Per the SSE spec the last event id is *not* reset here — it + persists across connections. + """ + self._event = "" + self._data = [] + self._retry = None + + def decode(self, line: str) -> Optional[ServerSentEvent]: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = "" + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None diff --git a/src/arcmira/core/http_sse/_exceptions.py b/src/arcmira/core/http_sse/_exceptions.py new file mode 100644 index 0000000..81605a8 --- /dev/null +++ b/src/arcmira/core/http_sse/_exceptions.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import httpx + + +class SSEError(httpx.TransportError): + pass diff --git a/src/arcmira/core/http_sse/_models.py b/src/arcmira/core/http_sse/_models.py new file mode 100644 index 0000000..1af57f8 --- /dev/null +++ b/src/arcmira/core/http_sse/_models.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass(frozen=True) +class ServerSentEvent: + event: str = "message" + data: str = "" + id: str = "" + retry: Optional[int] = None + + def json(self) -> Any: + """Parse the data field as JSON.""" + return json.loads(self.data) diff --git a/src/arcmira/core/jsonable_encoder.py b/src/arcmira/core/jsonable_encoder.py new file mode 100644 index 0000000..f638cc9 --- /dev/null +++ b/src/arcmira/core/jsonable_encoder.py @@ -0,0 +1,133 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union +from urllib.parse import quote + +import pydantic +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + encode_by_type, + to_jsonable_with_fallback, +) + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT". + # OMIT values should be excluded from serialized payloads. + if obj is Ellipsis: + return None + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + if value is Ellipsis: + continue + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + if item is Ellipsis: + continue + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) + + +def encode_path_param(obj: Any) -> str: + """Encode a value for use in a URL path segment. + + Ensures proper string conversion for all types, including + booleans which need lowercase 'true'/'false' rather than + Python's 'True'/'False'. + """ + if isinstance(obj, bool): + return "true" if obj else "false" + return str(jsonable_encoder(obj)) + + +def quote_path_param(obj: Any) -> str: + """Encode a value for use in a URL path segment, percent-encoding it. + + Same as encode_path_param, except the result is percent-encoded so + that a value containing "/" or ".." cannot change which endpoint + the request resolves to. + """ + if isinstance(obj, bool): + return "true" if obj else "false" + return quote(str(jsonable_encoder(obj)), safe="") diff --git a/src/arcmira/core/logging.py b/src/arcmira/core/logging.py new file mode 100644 index 0000000..e5e5724 --- /dev/null +++ b/src/arcmira/core/logging.py @@ -0,0 +1,107 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging +import typing + +LogLevel = typing.Literal["debug", "info", "warn", "error"] + +_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = { + "debug": 1, + "info": 2, + "warn": 3, + "error": 4, +} + + +class ILogger(typing.Protocol): + def debug(self, message: str, **kwargs: typing.Any) -> None: ... + def info(self, message: str, **kwargs: typing.Any) -> None: ... + def warn(self, message: str, **kwargs: typing.Any) -> None: ... + def error(self, message: str, **kwargs: typing.Any) -> None: ... + + +class ConsoleLogger: + _logger: logging.Logger + + def __init__(self) -> None: + self._logger = logging.getLogger("fern") + if not self._logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s")) + self._logger.addHandler(handler) + self._logger.setLevel(logging.DEBUG) + + def debug(self, message: str, **kwargs: typing.Any) -> None: + self._logger.debug(message, extra=kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + self._logger.info(message, extra=kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + self._logger.warning(message, extra=kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + self._logger.error(message, extra=kwargs) + + +class LogConfig(typing.TypedDict, total=False): + level: LogLevel + logger: ILogger + silent: bool + + +class Logger: + _level: int + _logger: ILogger + _silent: bool + + def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None: + self._level = _LOG_LEVEL_MAP[level] + self._logger = logger + self._silent = silent + + def _should_log(self, level: LogLevel) -> bool: + return not self._silent and self._level <= _LOG_LEVEL_MAP[level] + + def is_debug(self) -> bool: + return self._should_log("debug") + + def is_info(self) -> bool: + return self._should_log("info") + + def is_warn(self) -> bool: + return self._should_log("warn") + + def is_error(self) -> bool: + return self._should_log("error") + + def debug(self, message: str, **kwargs: typing.Any) -> None: + if self.is_debug(): + self._logger.debug(message, **kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + if self.is_info(): + self._logger.info(message, **kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + if self.is_warn(): + self._logger.warn(message, **kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + if self.is_error(): + self._logger.error(message, **kwargs) + + +_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True) + + +def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger: + if config is None: + return _default_logger + if isinstance(config, Logger): + return config + return Logger( + level=config.get("level", "info"), + logger=config.get("logger", ConsoleLogger()), + silent=config.get("silent", True), + ) diff --git a/src/arcmira/core/pagination.py b/src/arcmira/core/pagination.py new file mode 100644 index 0000000..760b089 --- /dev/null +++ b/src/arcmira/core/pagination.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import AsyncIterator, Awaitable, Callable, Generic, Iterator, List, Optional, TypeVar + +# Generic to represent the underlying type of the results within a page +T = TypeVar("T") +# Generic to represent the type of the API response +R = TypeVar("R") + + +# SDKs implement a Page ABC per-pagination request, the endpoint then returns a pager that wraps this type +# for example, an endpoint will return SyncPager[UserPage] where UserPage implements the Page ABC. ex: +# +# SyncPager( +# has_next=response.list_metadata.after is not None, +# items=response.data, +# # This should be the outer function that returns the SyncPager again +# get_next=lambda: list(..., cursor: response.cursor) (or list(..., offset: offset + 1)) +# ) + + +@dataclass(frozen=True) +class SyncPager(Generic[T, R]): + get_next: Optional[Callable[[], Optional[SyncPager[T, R]]]] + has_next: bool + items: Optional[List[T]] + response: R + + # Here we type ignore the iterator to avoid a mypy error + # caused by the type conflict with Pydanitc's __iter__ method + # brought in by extending the base model + def __iter__(self) -> Iterator[T]: # type: ignore[override] + for page in self.iter_pages(): + if page.items is not None: + yield from page.items + + def iter_pages(self) -> Iterator[SyncPager[T, R]]: + page: Optional[SyncPager[T, R]] = self + while page is not None: + yield page + + if not page.has_next or page.get_next is None: + return + + page = page.get_next() + if page is None or page.items is None or len(page.items) == 0: + return + + def next_page(self) -> Optional[SyncPager[T, R]]: + return self.get_next() if self.get_next is not None else None + + +@dataclass(frozen=True) +class AsyncPager(Generic[T, R]): + get_next: Optional[Callable[[], Awaitable[Optional[AsyncPager[T, R]]]]] + has_next: bool + items: Optional[List[T]] + response: R + + async def __aiter__(self) -> AsyncIterator[T]: + async for page in self.iter_pages(): + if page.items is not None: + for item in page.items: + yield item + + async def iter_pages(self) -> AsyncIterator[AsyncPager[T, R]]: + page: Optional[AsyncPager[T, R]] = self + while page is not None: + yield page + + if not page.has_next or page.get_next is None: + return + + page = await page.get_next() + if page is None or page.items is None or len(page.items) == 0: + return + + async def next_page(self) -> Optional[AsyncPager[T, R]]: + return await self.get_next() if self.get_next is not None else None diff --git a/src/arcmira/core/parse_error.py b/src/arcmira/core/parse_error.py new file mode 100644 index 0000000..4527c6a --- /dev/null +++ b/src/arcmira/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/arcmira/core/pydantic_utilities.py b/src/arcmira/core/pydantic_utilities.py new file mode 100644 index 0000000..70816b9 --- /dev/null +++ b/src/arcmira/core/pydantic_utilities.py @@ -0,0 +1,486 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import inspect +import json +import logging +import weakref +from collections import defaultdict +from dataclasses import asdict +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +import pydantic +import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo + +_logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from .http_sse._models import ServerSentEvent + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] + _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] + + def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value + return _datetime_adapter.validate_python(value) + + def parse_date(value: Any) -> dt.date: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + return _date_adapter.validate_python(value) + + # 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, 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, assignment] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: + """ + Parse a ServerSentEvent into the appropriate type. + + This function handles data-level discrimination where the discriminator + (e.g., 'type') is inside the 'data' payload. It parses the SSE data field + as JSON and deserializes it into the target type. + + Note: Protocol-level discrimination (where the discriminator comes from + the SSE event: field) is handled at code-generation time and does not + use this function. + + Args: + sse: The ServerSentEvent object to parse + type_: The target type to deserialize into + + Returns: + The parsed object of type T + + Note: + This function is only available in SDK contexts where http_sse module exists. + """ + sse_event = asdict(sse) + data_value = sse_event.get("data") + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + +_type_adapter_cache: Dict[int, Any] = {} + + +def _get_type_adapter(type_: Type[Any]) -> Any: + key = id(type_) + adapter = _type_adapter_cache.get(key) + if adapter is None: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + _type_adapter_cache[key] = adapter + return adapter + + +_field_alias_cache: "weakref.WeakKeyDictionary[type, Tuple[Dict[str, str], Tuple[str, ...]]]" = ( + weakref.WeakKeyDictionary() +) + + +def _get_field_aliases(model: type) -> Tuple[Dict[str, str], Tuple[str, ...]]: + """ + Map of field name to Pydantic alias for the fields whose alias differs from their name, together with the + keys that are ambiguous (an alias of one field and the name of another). Computed once per model class. + """ + cached = _field_alias_cache.get(model) + if cached is None: + fields: Mapping[str, Any] = ( + getattr(model, "model_fields", {}) if IS_PYDANTIC_V2 else getattr(model, "__fields__", {}) + ) + name_to_alias: Dict[str, str] = {} + for name, field in fields.items(): + alias = getattr(field, "alias", None) + if alias is not None and alias != name: + name_to_alias[name] = alias + cached = (name_to_alias, tuple(alias for alias in name_to_alias.values() if alias in fields)) + _field_alias_cache[model] = cached + return cached + + +def _coerce_keys_to_aliases(model: type, data: Any) -> Any: + """ + Accept Python field names in input by rewriting them to their Pydantic aliases, + while avoiding silent collisions when a key could refer to multiple fields. + """ + if not isinstance(data, Mapping): + return data + + name_to_alias, ambiguous_keys = _get_field_aliases(model) + for key in ambiguous_keys: + if key in data and name_to_alias.get(key, key) not in data: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + if not name_to_alias or not any(name in data for name in name_to_alias): + return data if isinstance(data, dict) else dict(data) + + rewritten: Dict[str, Any] = dict(data) + for name, alias in name_to_alias.items(): + if name in data and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + # convert_and_respect_annotation_metadata is required for TypedDict aliasing. + # + # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing: + # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through + # unchanged so Pydantic can validate them. + # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic + # will not recognize those aliases during validation. + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + has_pydantic_aliases = bool(_get_field_aliases(type_)[0]) + + dealiased_object = ( + object_ + if has_pydantic_aliases + else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + ) + else: + dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + if IS_PYDANTIC_V2: + adapter = _get_type_adapter(type_) + return adapter.validate_python(dealiased_object) # type: ignore[no-any-return] + return pydantic.parse_obj_as(type_, dealiased_object) + + +def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_validator(mode="before") # type: ignore[attr-defined] + @classmethod + def _coerce_field_names_to_aliases(cls, data: Any) -> Any: + return _coerce_keys_to_aliases(cls, data) + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + serialized = self.dict() # type: ignore[attr-defined] + data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @pydantic.root_validator(pre=True) + def _coerce_field_names_to_aliases(cls, values: Any) -> Any: + return _coerce_keys_to_aliases(cls, values) # type: ignore[arg-type] + + @classmethod + def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] + return super().construct(_fields_set, **dealiased_object) + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + return cast( + Dict[str, Any], + convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"), + ) + + +def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, _FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/src/arcmira/core/query_encoder.py b/src/arcmira/core/query_encoder.py new file mode 100644 index 0000000..3183001 --- /dev/null +++ b/src/arcmira/core/query_encoder.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, List, Optional, Tuple + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: + result = [] + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, arr_v)) + else: + result.append((key, v)) + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value + + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, value)) + + return encoded_values + + return [(query_key, query_value)] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/src/arcmira/core/remove_none_from_dict.py b/src/arcmira/core/remove_none_from_dict.py new file mode 100644 index 0000000..c229814 --- /dev/null +++ b/src/arcmira/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/src/arcmira/core/request_options.py b/src/arcmira/core/request_options.py new file mode 100644 index 0000000..caa6f66 --- /dev/null +++ b/src/arcmira/core/request_options.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout: float. The number of seconds to await an API call before timing out. + + - timeout_in_seconds: int. Deprecated alias for `timeout`; both are in seconds. Prefer `timeout`. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + + - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. + """ + + timeout: NotRequired[float] + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] + chunk_size: NotRequired[int] + stream_reconnection_enabled: NotRequired[bool] + max_stream_reconnection_attempts: NotRequired[int] diff --git a/src/arcmira/core/serialization.py b/src/arcmira/core/serialization.py new file mode 100644 index 0000000..1d753e2 --- /dev/null +++ b/src/arcmira/core/serialization.py @@ -0,0 +1,347 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +# Resolving type hints (typing.get_type_hints) is expensive because it eval/compiles +# forward-reference annotations. The result is constant for a given type, so we cache it. +# This is critical for hot paths like SSE event parsing, where the same (often large +# discriminated-union) type is converted on every single event. +_type_hints_cache: typing.Dict[typing.Any, typing.Dict[str, typing.Any]] = {} + + +def _get_cached_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]: + try: + cached = _type_hints_cache.get(expected_type) + except TypeError: + # Unhashable type; resolve without caching. + return _resolve_type_hints(expected_type) + if cached is None: + cached = _resolve_type_hints(expected_type) + _type_hints_cache[expected_type] = cached + return cached + + +def _resolve_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]: + try: + return typing_extensions.get_type_hints(expected_type, include_extras=True) + except NameError: + # The type contains a circular reference, so we use the __annotations__ attribute directly. + return getattr(expected_type, "__annotations__", {}) + + +# Whether convert_and_respect_annotation_metadata can possibly rewrite anything for a given +# annotation, i.e. whether any reachable model/TypedDict field carries a FieldMetadata alias. +# This is constant per type, so we cache it and use it to short-circuit the recursive walk. +_requires_conversion_cache: typing.Dict[typing.Any, bool] = {} + + +def _requires_conversion(type_: typing.Any) -> bool: + try: + cached = _requires_conversion_cache.get(type_) + except TypeError: + # Unhashable annotation; compute without caching. + return _compute_requires_conversion(type_, set()) + if cached is None: + cached = _compute_requires_conversion(type_, set()) + _requires_conversion_cache[type_] = cached + return cached + + +def _compute_requires_conversion(type_: typing.Any, seen: typing.Set[typing.Any]) -> bool: + clean_type = _remove_annotations(type_) + + try: + if clean_type in seen: + return False + seen = seen | {clean_type} + except TypeError: + # Unhashable type; skip cycle tracking (the type graph is finite in practice). + pass + + # Models / TypedDicts: a field alias here means we must dealias; otherwise recurse into fields. + if (inspect.isclass(clean_type) and issubclass(clean_type, pydantic.BaseModel)) or typing_extensions.is_typeddict( + clean_type + ): + annotations = _get_cached_type_hints(clean_type) + if _get_alias_to_field_name(annotations): + return True + return any(_compute_requires_conversion(hint, seen) for hint in annotations.values()) + + # Containers / unions: recurse into the type arguments (List/Set/Sequence/Dict/Union/etc.). + return any(_compute_requires_conversion(arg, seen) for arg in typing_extensions.get_args(clean_type)) + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + # The only thing this function ever rewrites is keys that carry a FieldMetadata + # alias. If nothing in the (cached) type graph has such an alias, the conversion is + # a content-identity transform, so we can skip the entire recursive walk. This is + # the hot path for SSE streaming, where a large discriminated union would otherwise + # be traversed on every single event. + if not _requires_conversion(annotation): + return object_ + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = _get_cached_type_hints(expected_type) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = _get_cached_type_hints(type_) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = _get_cached_type_hints(type_) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/src/arcmira/corrections/__init__.py b/src/arcmira/corrections/__init__.py new file mode 100644 index 0000000..e74377b --- /dev/null +++ b/src/arcmira/corrections/__init__.py @@ -0,0 +1,37 @@ +# 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 SubmitCorrectionsRequestAnchor, SubmitCorrectionsRequestKind +_dynamic_imports: typing.Dict[str, str] = { + "SubmitCorrectionsRequestAnchor": ".types", + "SubmitCorrectionsRequestKind": ".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__ = ["SubmitCorrectionsRequestAnchor", "SubmitCorrectionsRequestKind"] diff --git a/src/arcmira/corrections/client.py b/src/arcmira/corrections/client.py new file mode 100644 index 0000000..75e6e22 --- /dev/null +++ b/src/arcmira/corrections/client.py @@ -0,0 +1,404 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.correction_accepted_response import CorrectionAcceptedResponse +from ..types.withdrawn_response import WithdrawnResponse +from .raw_client import AsyncRawCorrectionsClient, RawCorrectionsClient +from .types.submit_corrections_request_anchor import SubmitCorrectionsRequestAnchor +from .types.submit_corrections_request_kind import SubmitCorrectionsRequestKind + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CorrectionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCorrectionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCorrectionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCorrectionsClient + """ + return self._raw_client + + def submit( + self, + video_id: str, + *, + kind: SubmitCorrectionsRequestKind, + payload: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + seq: typing.Optional[int] = OMIT, + revision: typing.Optional[str] = OMIT, + anchor: typing.Optional[SubmitCorrectionsRequestAnchor] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CorrectionAcceptedResponse: + """ + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + kind : SubmitCorrectionsRequestKind + + payload : typing.Dict[str, typing.Any] + Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. + + idempotency_key : typing.Optional[str] + Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + + seq : typing.Optional[int] + Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. + + revision : typing.Optional[str] + The revision from the Premium transcript read this correction was made against. Required for every kind. Speaker ids in the payload are the speakers[].id values of that read. + + anchor : typing.Optional[SubmitCorrectionsRequestAnchor] + Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CorrectionAcceptedResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.corrections.submit( + video_id="video_id", + kind="line_edit", + payload={"key": "value"}, + ) + """ + _response = self._raw_client.submit( + video_id, + kind=kind, + payload=payload, + idempotency_key=idempotency_key, + seq=seq, + revision=revision, + anchor=anchor, + request_options=request_options, + ) + return _response.data + + def withdraw_speaker_edit( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.corrections.withdraw_speaker_edit( + id="id", + ) + """ + _response = self._raw_client.withdraw_speaker_edit(id, request_options=request_options) + return _response.data + + def withdraw_entity_tag( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.corrections.withdraw_entity_tag( + id="id", + ) + """ + _response = self._raw_client.withdraw_entity_tag(id, request_options=request_options) + return _response.data + + def withdraw_segment_rewrite( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.corrections.withdraw_segment_rewrite( + id="id", + ) + """ + _response = self._raw_client.withdraw_segment_rewrite(id, request_options=request_options) + return _response.data + + +class AsyncCorrectionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCorrectionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCorrectionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCorrectionsClient + """ + return self._raw_client + + async def submit( + self, + video_id: str, + *, + kind: SubmitCorrectionsRequestKind, + payload: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + seq: typing.Optional[int] = OMIT, + revision: typing.Optional[str] = OMIT, + anchor: typing.Optional[SubmitCorrectionsRequestAnchor] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CorrectionAcceptedResponse: + """ + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + kind : SubmitCorrectionsRequestKind + + payload : typing.Dict[str, typing.Any] + Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. + + idempotency_key : typing.Optional[str] + Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + + seq : typing.Optional[int] + Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. + + revision : typing.Optional[str] + The revision from the Premium transcript read this correction was made against. Required for every kind. Speaker ids in the payload are the speakers[].id values of that read. + + anchor : typing.Optional[SubmitCorrectionsRequestAnchor] + Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CorrectionAcceptedResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.corrections.submit( + video_id="video_id", + kind="line_edit", + payload={"key": "value"}, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.submit( + video_id, + kind=kind, + payload=payload, + idempotency_key=idempotency_key, + seq=seq, + revision=revision, + anchor=anchor, + request_options=request_options, + ) + return _response.data + + async def withdraw_speaker_edit( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.corrections.withdraw_speaker_edit( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.withdraw_speaker_edit(id, request_options=request_options) + return _response.data + + async def withdraw_entity_tag( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.corrections.withdraw_entity_tag( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.withdraw_entity_tag(id, request_options=request_options) + return _response.data + + async def withdraw_segment_rewrite( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.corrections.withdraw_segment_rewrite( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.withdraw_segment_rewrite(id, request_options=request_options) + return _response.data diff --git a/src/arcmira/corrections/raw_client.py b/src/arcmira/corrections/raw_client.py new file mode 100644 index 0000000..50351d4 --- /dev/null +++ b/src/arcmira/corrections/raw_client.py @@ -0,0 +1,1025 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.conflict_error import ConflictError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.precondition_failed_error import PreconditionFailedError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.correction_accepted_response import CorrectionAcceptedResponse +from ..types.correction_seq_mismatch_response import CorrectionSeqMismatchResponse +from ..types.error import Error +from ..types.withdrawn_response import WithdrawnResponse +from .types.submit_corrections_request_anchor import SubmitCorrectionsRequestAnchor +from .types.submit_corrections_request_kind import SubmitCorrectionsRequestKind +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCorrectionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def submit( + self, + video_id: str, + *, + kind: SubmitCorrectionsRequestKind, + payload: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + seq: typing.Optional[int] = OMIT, + revision: typing.Optional[str] = OMIT, + anchor: typing.Optional[SubmitCorrectionsRequestAnchor] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CorrectionAcceptedResponse]: + """ + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + kind : SubmitCorrectionsRequestKind + + payload : typing.Dict[str, typing.Any] + Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. + + idempotency_key : typing.Optional[str] + Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + + seq : typing.Optional[int] + Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. + + revision : typing.Optional[str] + The revision from the Premium transcript read this correction was made against. Required for every kind. Speaker ids in the payload are the speakers[].id values of that read. + + anchor : typing.Optional[SubmitCorrectionsRequestAnchor] + Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CorrectionAcceptedResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/videos/{encode_path_param(video_id)}/corrections", + method="POST", + json={ + "kind": kind, + "seq": seq, + "revision": revision, + "anchor": convert_and_respect_annotation_metadata( + object_=anchor, annotation=SubmitCorrectionsRequestAnchor, direction="write" + ), + "payload": payload, + }, + 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( + CorrectionAcceptedResponse, + parse_obj_as( + type_=CorrectionAcceptedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + CorrectionSeqMismatchResponse, + parse_obj_as( + type_=CorrectionSeqMismatchResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw_speaker_edit( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/corrections/speaker-edits/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw_entity_tag( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/corrections/entity-tags/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw_segment_rewrite( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/corrections/segment-rewrites/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawCorrectionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def submit( + self, + video_id: str, + *, + kind: SubmitCorrectionsRequestKind, + payload: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + seq: typing.Optional[int] = OMIT, + revision: typing.Optional[str] = OMIT, + anchor: typing.Optional[SubmitCorrectionsRequestAnchor] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CorrectionAcceptedResponse]: + """ + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + kind : SubmitCorrectionsRequestKind + + payload : typing.Dict[str, typing.Any] + Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. + + idempotency_key : typing.Optional[str] + Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + + seq : typing.Optional[int] + Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. + + revision : typing.Optional[str] + The revision from the Premium transcript read this correction was made against. Required for every kind. Speaker ids in the payload are the speakers[].id values of that read. + + anchor : typing.Optional[SubmitCorrectionsRequestAnchor] + Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CorrectionAcceptedResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/videos/{encode_path_param(video_id)}/corrections", + method="POST", + json={ + "kind": kind, + "seq": seq, + "revision": revision, + "anchor": convert_and_respect_annotation_metadata( + object_=anchor, annotation=SubmitCorrectionsRequestAnchor, direction="write" + ), + "payload": payload, + }, + 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( + CorrectionAcceptedResponse, + parse_obj_as( + type_=CorrectionAcceptedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + CorrectionSeqMismatchResponse, + parse_obj_as( + type_=CorrectionSeqMismatchResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw_speaker_edit( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/corrections/speaker-edits/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw_entity_tag( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/corrections/entity-tags/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw_segment_rewrite( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/corrections/segment-rewrites/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/corrections/types/__init__.py b/src/arcmira/corrections/types/__init__.py new file mode 100644 index 0000000..17c31dd --- /dev/null +++ b/src/arcmira/corrections/types/__init__.py @@ -0,0 +1,38 @@ +# 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 .submit_corrections_request_anchor import SubmitCorrectionsRequestAnchor + from .submit_corrections_request_kind import SubmitCorrectionsRequestKind +_dynamic_imports: typing.Dict[str, str] = { + "SubmitCorrectionsRequestAnchor": ".submit_corrections_request_anchor", + "SubmitCorrectionsRequestKind": ".submit_corrections_request_kind", +} + + +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__ = ["SubmitCorrectionsRequestAnchor", "SubmitCorrectionsRequestKind"] diff --git a/src/arcmira/corrections/types/submit_corrections_request_anchor.py b/src/arcmira/corrections/types/submit_corrections_request_anchor.py new file mode 100644 index 0000000..0df5e43 --- /dev/null +++ b/src/arcmira/corrections/types/submit_corrections_request_anchor.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class SubmitCorrectionsRequestAnchor(UniversalBaseModel): + """ + Required for line_edit, speaker_reassign, entity_tag, and segment_rewrite. + """ + + segment_index: typing_extensions.Annotated[ + int, FieldMetadata(alias="segmentIndex"), pydantic.Field(alias="segmentIndex") + ] + content_hash: typing_extensions.Annotated[ + str, + FieldMetadata(alias="contentHash"), + pydantic.Field( + alias="contentHash", + description="djb2 hash of the covered segment text (joined with \\n for ranges), computed against your projected view of the transcript.", + ), + ] + """ + djb2 hash of the covered segment text (joined with \\n for ranges), computed against your projected view of the transcript. + """ + + 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/arcmira/corrections/types/submit_corrections_request_kind.py b/src/arcmira/corrections/types/submit_corrections_request_kind.py new file mode 100644 index 0000000..2c148b1 --- /dev/null +++ b/src/arcmira/corrections/types/submit_corrections_request_kind.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubmitCorrectionsRequestKind = typing.Union[ + typing.Literal["line_edit", "speaker_reassign", "speaker_identify", "add_person", "entity_tag", "segment_rewrite"], + typing.Any, +] diff --git a/src/arcmira/entities/__init__.py b/src/arcmira/entities/__init__.py new file mode 100644 index 0000000..acf9d5a --- /dev/null +++ b/src/arcmira/entities/__init__.py @@ -0,0 +1,72 @@ +# 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 ( + LookupEntitiesRequestType, + MomentumEntitiesRequestSrc, + ResolveEntitiesRequestSrc, + ResolveEntitiesRequestType, + SearchEntitiesRequestSrc, + SearchEntitiesRequestType, + ) + from . import mentions, recommendations + from .mentions import ListMentionsRequestDetails, ListMentionsRequestSentiment, ListMentionsRequestSrc + from .recommendations import ListRecommendationsRequestMentionClass, ListRecommendationsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ListMentionsRequestDetails": ".mentions", + "ListMentionsRequestSentiment": ".mentions", + "ListMentionsRequestSrc": ".mentions", + "ListRecommendationsRequestMentionClass": ".recommendations", + "ListRecommendationsRequestSrc": ".recommendations", + "LookupEntitiesRequestType": ".types", + "MomentumEntitiesRequestSrc": ".types", + "ResolveEntitiesRequestSrc": ".types", + "ResolveEntitiesRequestType": ".types", + "SearchEntitiesRequestSrc": ".types", + "SearchEntitiesRequestType": ".types", + "mentions": ".mentions", + "recommendations": ".recommendations", +} + + +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__ = [ + "ListMentionsRequestDetails", + "ListMentionsRequestSentiment", + "ListMentionsRequestSrc", + "ListRecommendationsRequestMentionClass", + "ListRecommendationsRequestSrc", + "LookupEntitiesRequestType", + "MomentumEntitiesRequestSrc", + "ResolveEntitiesRequestSrc", + "ResolveEntitiesRequestType", + "SearchEntitiesRequestSrc", + "SearchEntitiesRequestType", + "mentions", + "recommendations", +] diff --git a/src/arcmira/entities/client.py b/src/arcmira/entities/client.py new file mode 100644 index 0000000..9318184 --- /dev/null +++ b/src/arcmira/entities/client.py @@ -0,0 +1,643 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.entity_cards_response import EntityCardsResponse +from ..types.entity_detail_response import EntityDetailResponse +from ..types.entity_lookup_response import EntityLookupResponse +from ..types.entity_momentum_response import EntityMomentumResponse +from ..types.entity_resolve_response import EntityResolveResponse +from ..types.entity_search_response import EntitySearchResponse +from .raw_client import AsyncRawEntitiesClient, RawEntitiesClient +from .types.lookup_entities_request_type import LookupEntitiesRequestType +from .types.momentum_entities_request_src import MomentumEntitiesRequestSrc +from .types.resolve_entities_request_src import ResolveEntitiesRequestSrc +from .types.resolve_entities_request_type import ResolveEntitiesRequestType +from .types.search_entities_request_src import SearchEntitiesRequestSrc +from .types.search_entities_request_type import SearchEntitiesRequestType + +if typing.TYPE_CHECKING: + from .mentions.client import AsyncMentionsClient, MentionsClient + from .recommendations.client import AsyncRecommendationsClient, RecommendationsClient + + +class EntitiesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawEntitiesClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._mentions: typing.Optional[MentionsClient] = None + self._recommendations: typing.Optional[RecommendationsClient] = None + + @property + def with_raw_response(self) -> RawEntitiesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawEntitiesClient + """ + return self._raw_client + + def search( + self, + *, + q: str, + type: typing.Optional[SearchEntitiesRequestType] = None, + has_recommendations_data: typing.Optional[bool] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntitySearchResponse: + """ + Substring name search returning up to 25 entities ordered by appearance count, each with a suggested flag: true on an exact match with far more traction than any other row of its type. A q that is a YouTube channel id (UC...), an @handle, or a YouTube URL carrying either resolves to that one channel row, suggested, with its youtube_channel_id. Callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary per result when a brand profile exists. + + Parameters + ---------- + q : str + + type : typing.Optional[SearchEntitiesRequestType] + + has_recommendations_data : typing.Optional[bool] + + limit : typing.Optional[int] + + src : typing.Optional[SearchEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntitySearchResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.entities.search( + q="q", + ) + """ + _response = self._raw_client.search( + q=q, + type=type, + has_recommendations_data=has_recommendations_data, + limit=limit, + src=src, + request_options=request_options, + ) + return _response.data + + def resolve( + self, + *, + q: str, + type: typing.Optional[ResolveEntitiesRequestType] = None, + limit: typing.Optional[int] = None, + context: typing.Optional[str] = None, + src: typing.Optional[ResolveEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntityResolveResponse: + """ + Call this before passing an id to about, by, entity_ids, channel_ids or channel; those filters refuse names with id_required. Pass context with the user's own words about the name ("the startup bank", "on My First Million"). The answer is one of three: best (the name means one row: use it and name it), suggested (no row is certain but one stands out, with reason and evidence: use it and tell the user you assumed it), or ask (several rows fit: show ask.options, or check every option id and answer per row). For a show pass type=channel and use the youtube_channel_id; for a brand or a person use the id. Free; bills no rows. + + Parameters + ---------- + q : str + A name, @handle, YouTube URL or channel id (UC...). One thing per call. + + type : typing.Optional[ResolveEntitiesRequestType] + Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id. + + limit : typing.Optional[int] + Candidates to return, 1 to 15. Default 8. + + context : typing.Optional[str] + What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. + + src : typing.Optional[ResolveEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityResolveResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.entities.resolve( + q="q", + ) + """ + _response = self._raw_client.resolve( + q=q, type=type, limit=limit, context=context, src=src, request_options=request_options + ) + return _response.data + + def lookup( + self, + *, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + type: typing.Optional[LookupEntitiesRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntityLookupResponse: + """ + Resolves an id or name to the canonical entity record, following merge redirects. Pass either id (ent_{n} or numeric) or name, optionally constrained by type. + + Parameters + ---------- + id : typing.Optional[str] + + name : typing.Optional[str] + + type : typing.Optional[LookupEntitiesRequestType] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityLookupResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.entities.lookup() + """ + _response = self._raw_client.lookup(id=id, name=name, type=type, request_options=request_options) + return _response.data + + def cards(self, *, ids: str, request_options: typing.Optional[RequestOptions] = None) -> EntityCardsResponse: + """ + Free endpoint (0 rows). Batched compact lookups for UI hover cards: name, slug, type, image, and index counts for up to 50 raw integer entity ids per request. Merged ids resolve to their canonical entity but are returned under the requested id; unknown ids are silently dropped. The response is identical for all viewers and CDN-cacheable. + + Parameters + ---------- + ids : str + Comma-separated raw integer entity ids, 1 to 50 of them (e.g. "12,844,1032"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityCardsResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.entities.cards( + ids="ids", + ) + """ + _response = self._raw_client.cards(ids=ids, request_options=request_options) + return _response.data + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> EntityDetailResponse: + """ + Returns the canonical entity envelope for an ent_{n} or numeric id, following merge redirects. For organization and product entities, callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary commercial-intelligence rollup when a brand profile exists. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityDetailResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.entities.get( + id="id", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def momentum( + self, + id: str, + *, + src: typing.Optional[MomentumEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntityMomentumResponse: + """ + Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + src : typing.Optional[MomentumEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityMomentumResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.entities.momentum( + id="id", + ) + """ + _response = self._raw_client.momentum(id, src=src, request_options=request_options) + return _response.data + + @property + def mentions(self): + if self._mentions is None: + from .mentions.client import MentionsClient # noqa: E402 + + self._mentions = MentionsClient(client_wrapper=self._client_wrapper) + return self._mentions + + @property + def recommendations(self): + if self._recommendations is None: + from .recommendations.client import RecommendationsClient # noqa: E402 + + self._recommendations = RecommendationsClient(client_wrapper=self._client_wrapper) + return self._recommendations + + +class AsyncEntitiesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawEntitiesClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._mentions: typing.Optional[AsyncMentionsClient] = None + self._recommendations: typing.Optional[AsyncRecommendationsClient] = None + + @property + def with_raw_response(self) -> AsyncRawEntitiesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawEntitiesClient + """ + return self._raw_client + + async def search( + self, + *, + q: str, + type: typing.Optional[SearchEntitiesRequestType] = None, + has_recommendations_data: typing.Optional[bool] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntitySearchResponse: + """ + Substring name search returning up to 25 entities ordered by appearance count, each with a suggested flag: true on an exact match with far more traction than any other row of its type. A q that is a YouTube channel id (UC...), an @handle, or a YouTube URL carrying either resolves to that one channel row, suggested, with its youtube_channel_id. Callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary per result when a brand profile exists. + + Parameters + ---------- + q : str + + type : typing.Optional[SearchEntitiesRequestType] + + has_recommendations_data : typing.Optional[bool] + + limit : typing.Optional[int] + + src : typing.Optional[SearchEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntitySearchResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.entities.search( + q="q", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.search( + q=q, + type=type, + has_recommendations_data=has_recommendations_data, + limit=limit, + src=src, + request_options=request_options, + ) + return _response.data + + async def resolve( + self, + *, + q: str, + type: typing.Optional[ResolveEntitiesRequestType] = None, + limit: typing.Optional[int] = None, + context: typing.Optional[str] = None, + src: typing.Optional[ResolveEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntityResolveResponse: + """ + Call this before passing an id to about, by, entity_ids, channel_ids or channel; those filters refuse names with id_required. Pass context with the user's own words about the name ("the startup bank", "on My First Million"). The answer is one of three: best (the name means one row: use it and name it), suggested (no row is certain but one stands out, with reason and evidence: use it and tell the user you assumed it), or ask (several rows fit: show ask.options, or check every option id and answer per row). For a show pass type=channel and use the youtube_channel_id; for a brand or a person use the id. Free; bills no rows. + + Parameters + ---------- + q : str + A name, @handle, YouTube URL or channel id (UC...). One thing per call. + + type : typing.Optional[ResolveEntitiesRequestType] + Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id. + + limit : typing.Optional[int] + Candidates to return, 1 to 15. Default 8. + + context : typing.Optional[str] + What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. + + src : typing.Optional[ResolveEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityResolveResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.entities.resolve( + q="q", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.resolve( + q=q, type=type, limit=limit, context=context, src=src, request_options=request_options + ) + return _response.data + + async def lookup( + self, + *, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + type: typing.Optional[LookupEntitiesRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntityLookupResponse: + """ + Resolves an id or name to the canonical entity record, following merge redirects. Pass either id (ent_{n} or numeric) or name, optionally constrained by type. + + Parameters + ---------- + id : typing.Optional[str] + + name : typing.Optional[str] + + type : typing.Optional[LookupEntitiesRequestType] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityLookupResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.entities.lookup() + + + asyncio.run(main()) + """ + _response = await self._raw_client.lookup(id=id, name=name, type=type, request_options=request_options) + return _response.data + + async def cards(self, *, ids: str, request_options: typing.Optional[RequestOptions] = None) -> EntityCardsResponse: + """ + Free endpoint (0 rows). Batched compact lookups for UI hover cards: name, slug, type, image, and index counts for up to 50 raw integer entity ids per request. Merged ids resolve to their canonical entity but are returned under the requested id; unknown ids are silently dropped. The response is identical for all viewers and CDN-cacheable. + + Parameters + ---------- + ids : str + Comma-separated raw integer entity ids, 1 to 50 of them (e.g. "12,844,1032"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityCardsResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.entities.cards( + ids="ids", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.cards(ids=ids, request_options=request_options) + return _response.data + + async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> EntityDetailResponse: + """ + Returns the canonical entity envelope for an ent_{n} or numeric id, following merge redirects. For organization and product entities, callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary commercial-intelligence rollup when a brand profile exists. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityDetailResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.entities.get( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def momentum( + self, + id: str, + *, + src: typing.Optional[MomentumEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EntityMomentumResponse: + """ + Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + src : typing.Optional[MomentumEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EntityMomentumResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.entities.momentum( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.momentum(id, src=src, request_options=request_options) + return _response.data + + @property + def mentions(self): + if self._mentions is None: + from .mentions.client import AsyncMentionsClient # noqa: E402 + + self._mentions = AsyncMentionsClient(client_wrapper=self._client_wrapper) + return self._mentions + + @property + def recommendations(self): + if self._recommendations is None: + from .recommendations.client import AsyncRecommendationsClient # noqa: E402 + + self._recommendations = AsyncRecommendationsClient(client_wrapper=self._client_wrapper) + return self._recommendations diff --git a/src/arcmira/entities/mentions/__init__.py b/src/arcmira/entities/mentions/__init__.py new file mode 100644 index 0000000..ef945ab --- /dev/null +++ b/src/arcmira/entities/mentions/__init__.py @@ -0,0 +1,38 @@ +# 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 ListMentionsRequestDetails, ListMentionsRequestSentiment, ListMentionsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ListMentionsRequestDetails": ".types", + "ListMentionsRequestSentiment": ".types", + "ListMentionsRequestSrc": ".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__ = ["ListMentionsRequestDetails", "ListMentionsRequestSentiment", "ListMentionsRequestSrc"] diff --git a/src/arcmira/entities/mentions/client.py b/src/arcmira/entities/mentions/client.py new file mode 100644 index 0000000..78d3b3c --- /dev/null +++ b/src/arcmira/entities/mentions/client.py @@ -0,0 +1,232 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.mention import Mention +from ...types.mention_list_response import MentionListResponse +from .raw_client import AsyncRawMentionsClient, RawMentionsClient +from .types.list_mentions_request_details import ListMentionsRequestDetails +from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment +from .types.list_mentions_request_src import ListMentionsRequestSrc + + +class MentionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMentionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMentionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMentionsClient + """ + return self._raw_client + + def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions for one entity, newest media first. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Mention, MentionListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.entities.mentions.list( + id="id", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + id, + limit=limit, + cursor=cursor, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + + +class AsyncMentionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMentionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMentionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMentionsClient + """ + return self._raw_client + + async def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions for one entity, newest media first. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Mention, MentionListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.entities.mentions.list( + id="id", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + id, + limit=limit, + cursor=cursor, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) diff --git a/src/arcmira/entities/mentions/raw_client.py b/src/arcmira/entities/mentions/raw_client.py new file mode 100644 index 0000000..f3367c8 --- /dev/null +++ b/src/arcmira/entities/mentions/raw_client.py @@ -0,0 +1,417 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.mention import Mention +from ...types.mention_list_response import MentionListResponse +from .types.list_mentions_request_details import ListMentionsRequestDetails +from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment +from .types.list_mentions_request_src import ListMentionsRequestSrc +from pydantic import ValidationError + + +class RawMentionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions for one entity, newest media first. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Mention, MentionListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}/mentions", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "channel_id": channel_id, + "channel_name": channel_name, + "q": q, + "sentiment": sentiment, + "is_appearance": is_appearance, + "date_from": date_from, + "date_to": date_to, + "details": details, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + MentionListResponse, + parse_obj_as( + type_=MentionListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + id, + limit=limit, + cursor=_parsed_next, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawMentionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions for one entity, newest media first. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Mention, MentionListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}/mentions", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "channel_id": channel_id, + "channel_name": channel_name, + "q": q, + "sentiment": sentiment, + "is_appearance": is_appearance, + "date_from": date_from, + "date_to": date_to, + "details": details, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + MentionListResponse, + parse_obj_as( + type_=MentionListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + id, + limit=limit, + cursor=_parsed_next, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/entities/mentions/types/__init__.py b/src/arcmira/entities/mentions/types/__init__.py new file mode 100644 index 0000000..429126f --- /dev/null +++ b/src/arcmira/entities/mentions/types/__init__.py @@ -0,0 +1,40 @@ +# 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 .list_mentions_request_details import ListMentionsRequestDetails + from .list_mentions_request_sentiment import ListMentionsRequestSentiment + from .list_mentions_request_src import ListMentionsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ListMentionsRequestDetails": ".list_mentions_request_details", + "ListMentionsRequestSentiment": ".list_mentions_request_sentiment", + "ListMentionsRequestSrc": ".list_mentions_request_src", +} + + +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__ = ["ListMentionsRequestDetails", "ListMentionsRequestSentiment", "ListMentionsRequestSrc"] diff --git a/src/arcmira/entities/mentions/types/list_mentions_request_details.py b/src/arcmira/entities/mentions/types/list_mentions_request_details.py new file mode 100644 index 0000000..1b4c9c1 --- /dev/null +++ b/src/arcmira/entities/mentions/types/list_mentions_request_details.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestDetails = typing.Union[typing.Literal["full"], typing.Any] diff --git a/src/arcmira/entities/mentions/types/list_mentions_request_sentiment.py b/src/arcmira/entities/mentions/types/list_mentions_request_sentiment.py new file mode 100644 index 0000000..65a4df7 --- /dev/null +++ b/src/arcmira/entities/mentions/types/list_mentions_request_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/entities/mentions/types/list_mentions_request_src.py b/src/arcmira/entities/mentions/types/list_mentions_request_src.py new file mode 100644 index 0000000..84409bd --- /dev/null +++ b/src/arcmira/entities/mentions/types/list_mentions_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/raw_client.py b/src/arcmira/entities/raw_client.py new file mode 100644 index 0000000..3cc4e3a --- /dev/null +++ b/src/arcmira/entities/raw_client.py @@ -0,0 +1,1587 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.entity_cards_response import EntityCardsResponse +from ..types.entity_detail_response import EntityDetailResponse +from ..types.entity_lookup_response import EntityLookupResponse +from ..types.entity_momentum_response import EntityMomentumResponse +from ..types.entity_resolve_response import EntityResolveResponse +from ..types.entity_search_response import EntitySearchResponse +from ..types.error import Error +from .types.lookup_entities_request_type import LookupEntitiesRequestType +from .types.momentum_entities_request_src import MomentumEntitiesRequestSrc +from .types.resolve_entities_request_src import ResolveEntitiesRequestSrc +from .types.resolve_entities_request_type import ResolveEntitiesRequestType +from .types.search_entities_request_src import SearchEntitiesRequestSrc +from .types.search_entities_request_type import SearchEntitiesRequestType +from pydantic import ValidationError + + +class RawEntitiesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def search( + self, + *, + q: str, + type: typing.Optional[SearchEntitiesRequestType] = None, + has_recommendations_data: typing.Optional[bool] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EntitySearchResponse]: + """ + Substring name search returning up to 25 entities ordered by appearance count, each with a suggested flag: true on an exact match with far more traction than any other row of its type. A q that is a YouTube channel id (UC...), an @handle, or a YouTube URL carrying either resolves to that one channel row, suggested, with its youtube_channel_id. Callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary per result when a brand profile exists. + + Parameters + ---------- + q : str + + type : typing.Optional[SearchEntitiesRequestType] + + has_recommendations_data : typing.Optional[bool] + + limit : typing.Optional[int] + + src : typing.Optional[SearchEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EntitySearchResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/entities/search", + method="GET", + params={ + "q": q, + "type": type, + "has_recommendations_data": has_recommendations_data, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntitySearchResponse, + parse_obj_as( + type_=EntitySearchResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 resolve( + self, + *, + q: str, + type: typing.Optional[ResolveEntitiesRequestType] = None, + limit: typing.Optional[int] = None, + context: typing.Optional[str] = None, + src: typing.Optional[ResolveEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EntityResolveResponse]: + """ + Call this before passing an id to about, by, entity_ids, channel_ids or channel; those filters refuse names with id_required. Pass context with the user's own words about the name ("the startup bank", "on My First Million"). The answer is one of three: best (the name means one row: use it and name it), suggested (no row is certain but one stands out, with reason and evidence: use it and tell the user you assumed it), or ask (several rows fit: show ask.options, or check every option id and answer per row). For a show pass type=channel and use the youtube_channel_id; for a brand or a person use the id. Free; bills no rows. + + Parameters + ---------- + q : str + A name, @handle, YouTube URL or channel id (UC...). One thing per call. + + type : typing.Optional[ResolveEntitiesRequestType] + Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id. + + limit : typing.Optional[int] + Candidates to return, 1 to 15. Default 8. + + context : typing.Optional[str] + What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. + + src : typing.Optional[ResolveEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EntityResolveResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/entities/resolve", + method="GET", + params={ + "q": q, + "type": type, + "limit": limit, + "context": context, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityResolveResponse, + parse_obj_as( + type_=EntityResolveResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 lookup( + self, + *, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + type: typing.Optional[LookupEntitiesRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EntityLookupResponse]: + """ + Resolves an id or name to the canonical entity record, following merge redirects. Pass either id (ent_{n} or numeric) or name, optionally constrained by type. + + Parameters + ---------- + id : typing.Optional[str] + + name : typing.Optional[str] + + type : typing.Optional[LookupEntitiesRequestType] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EntityLookupResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/entities/lookup", + method="GET", + params={ + "id": id, + "name": name, + "type": type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityLookupResponse, + parse_obj_as( + type_=EntityLookupResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 cards( + self, *, ids: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[EntityCardsResponse]: + """ + Free endpoint (0 rows). Batched compact lookups for UI hover cards: name, slug, type, image, and index counts for up to 50 raw integer entity ids per request. Merged ids resolve to their canonical entity but are returned under the requested id; unknown ids are silently dropped. The response is identical for all viewers and CDN-cacheable. + + Parameters + ---------- + ids : str + Comma-separated raw integer entity ids, 1 to 50 of them (e.g. "12,844,1032"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EntityCardsResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/entities/cards", + method="GET", + params={ + "ids": ids, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityCardsResponse, + parse_obj_as( + type_=EntityCardsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[EntityDetailResponse]: + """ + Returns the canonical entity envelope for an ent_{n} or numeric id, following merge redirects. For organization and product entities, callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary commercial-intelligence rollup when a brand profile exists. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EntityDetailResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityDetailResponse, + parse_obj_as( + type_=EntityDetailResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 momentum( + self, + id: str, + *, + src: typing.Optional[MomentumEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EntityMomentumResponse]: + """ + Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + src : typing.Optional[MomentumEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EntityMomentumResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}/momentum", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityMomentumResponse, + parse_obj_as( + type_=EntityMomentumResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawEntitiesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def search( + self, + *, + q: str, + type: typing.Optional[SearchEntitiesRequestType] = None, + has_recommendations_data: typing.Optional[bool] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EntitySearchResponse]: + """ + Substring name search returning up to 25 entities ordered by appearance count, each with a suggested flag: true on an exact match with far more traction than any other row of its type. A q that is a YouTube channel id (UC...), an @handle, or a YouTube URL carrying either resolves to that one channel row, suggested, with its youtube_channel_id. Callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary per result when a brand profile exists. + + Parameters + ---------- + q : str + + type : typing.Optional[SearchEntitiesRequestType] + + has_recommendations_data : typing.Optional[bool] + + limit : typing.Optional[int] + + src : typing.Optional[SearchEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EntitySearchResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/entities/search", + method="GET", + params={ + "q": q, + "type": type, + "has_recommendations_data": has_recommendations_data, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntitySearchResponse, + parse_obj_as( + type_=EntitySearchResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 resolve( + self, + *, + q: str, + type: typing.Optional[ResolveEntitiesRequestType] = None, + limit: typing.Optional[int] = None, + context: typing.Optional[str] = None, + src: typing.Optional[ResolveEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EntityResolveResponse]: + """ + Call this before passing an id to about, by, entity_ids, channel_ids or channel; those filters refuse names with id_required. Pass context with the user's own words about the name ("the startup bank", "on My First Million"). The answer is one of three: best (the name means one row: use it and name it), suggested (no row is certain but one stands out, with reason and evidence: use it and tell the user you assumed it), or ask (several rows fit: show ask.options, or check every option id and answer per row). For a show pass type=channel and use the youtube_channel_id; for a brand or a person use the id. Free; bills no rows. + + Parameters + ---------- + q : str + A name, @handle, YouTube URL or channel id (UC...). One thing per call. + + type : typing.Optional[ResolveEntitiesRequestType] + Restrict candidates to one type. Pass channel for a show and read best.youtube_channel_id. + + limit : typing.Optional[int] + Candidates to return, 1 to 15. Default 8. + + context : typing.Optional[str] + What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. + + src : typing.Optional[ResolveEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EntityResolveResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/entities/resolve", + method="GET", + params={ + "q": q, + "type": type, + "limit": limit, + "context": context, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityResolveResponse, + parse_obj_as( + type_=EntityResolveResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 lookup( + self, + *, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + type: typing.Optional[LookupEntitiesRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EntityLookupResponse]: + """ + Resolves an id or name to the canonical entity record, following merge redirects. Pass either id (ent_{n} or numeric) or name, optionally constrained by type. + + Parameters + ---------- + id : typing.Optional[str] + + name : typing.Optional[str] + + type : typing.Optional[LookupEntitiesRequestType] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EntityLookupResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/entities/lookup", + method="GET", + params={ + "id": id, + "name": name, + "type": type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityLookupResponse, + parse_obj_as( + type_=EntityLookupResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 cards( + self, *, ids: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[EntityCardsResponse]: + """ + Free endpoint (0 rows). Batched compact lookups for UI hover cards: name, slug, type, image, and index counts for up to 50 raw integer entity ids per request. Merged ids resolve to their canonical entity but are returned under the requested id; unknown ids are silently dropped. The response is identical for all viewers and CDN-cacheable. + + Parameters + ---------- + ids : str + Comma-separated raw integer entity ids, 1 to 50 of them (e.g. "12,844,1032"). Merged ids resolve to their canonical entity; unknown ids are silently dropped from the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EntityCardsResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/entities/cards", + method="GET", + params={ + "ids": ids, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityCardsResponse, + parse_obj_as( + type_=EntityCardsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[EntityDetailResponse]: + """ + Returns the canonical entity envelope for an ent_{n} or numeric id, following merge redirects. For organization and product entities, callers with Recommendations API access (a Pro+ plan) also receive a recommendations_summary commercial-intelligence rollup when a brand profile exists. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EntityDetailResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityDetailResponse, + parse_obj_as( + type_=EntityDetailResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 momentum( + self, + id: str, + *, + src: typing.Optional[MomentumEntitiesRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EntityMomentumResponse]: + """ + Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + src : typing.Optional[MomentumEntitiesRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EntityMomentumResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}/momentum", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EntityMomentumResponse, + parse_obj_as( + type_=EntityMomentumResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/entities/recommendations/__init__.py b/src/arcmira/entities/recommendations/__init__.py new file mode 100644 index 0000000..4e59ee4 --- /dev/null +++ b/src/arcmira/entities/recommendations/__init__.py @@ -0,0 +1,37 @@ +# 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 ListRecommendationsRequestMentionClass, ListRecommendationsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ListRecommendationsRequestMentionClass": ".types", + "ListRecommendationsRequestSrc": ".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__ = ["ListRecommendationsRequestMentionClass", "ListRecommendationsRequestSrc"] diff --git a/src/arcmira/entities/recommendations/client.py b/src/arcmira/entities/recommendations/client.py new file mode 100644 index 0000000..0ec61a6 --- /dev/null +++ b/src/arcmira/entities/recommendations/client.py @@ -0,0 +1,223 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.recommendation import Recommendation +from ...types.recommendation_list_response import RecommendationListResponse +from .raw_client import AsyncRawRecommendationsClient, RawRecommendationsClient +from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass +from .types.list_recommendations_request_src import ListRecommendationsRequestSrc + + +class RecommendationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRecommendationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRecommendationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRecommendationsClient + """ + return self._raw_client + + def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Recommendation, RecommendationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.entities.recommendations.list( + id="id", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + id, + limit=limit, + cursor=cursor, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) + + +class AsyncRecommendationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRecommendationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRecommendationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRecommendationsClient + """ + return self._raw_client + + async def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Recommendation, RecommendationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.entities.recommendations.list( + id="id", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + id, + limit=limit, + cursor=cursor, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) diff --git a/src/arcmira/entities/recommendations/raw_client.py b/src/arcmira/entities/recommendations/raw_client.py new file mode 100644 index 0000000..4b50fd5 --- /dev/null +++ b/src/arcmira/entities/recommendations/raw_client.py @@ -0,0 +1,406 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.recommendation import Recommendation +from ...types.recommendation_list_response import RecommendationListResponse +from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass +from .types.list_recommendations_request_src import ListRecommendationsRequestSrc +from pydantic import ValidationError + + +class RawRecommendationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Recommendation, RecommendationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}/recommendations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "channel_id": channel_id, + "channel_name": channel_name, + "mention_class": mention_class, + "min_confidence": min_confidence, + "date_from": date_from, + "date_to": date_to, + "include_disputed": include_disputed, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + RecommendationListResponse, + parse_obj_as( + type_=RecommendationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + id, + limit=limit, + cursor=_parsed_next, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRecommendationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + id: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + + Parameters + ---------- + id : str + Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Recommendation, RecommendationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/entities/{encode_path_param(id)}/recommendations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "channel_id": channel_id, + "channel_name": channel_name, + "mention_class": mention_class, + "min_confidence": min_confidence, + "date_from": date_from, + "date_to": date_to, + "include_disputed": include_disputed, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + RecommendationListResponse, + parse_obj_as( + type_=RecommendationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + id, + limit=limit, + cursor=_parsed_next, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/entities/recommendations/types/__init__.py b/src/arcmira/entities/recommendations/types/__init__.py new file mode 100644 index 0000000..c3b247a --- /dev/null +++ b/src/arcmira/entities/recommendations/types/__init__.py @@ -0,0 +1,38 @@ +# 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 .list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass + from .list_recommendations_request_src import ListRecommendationsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ListRecommendationsRequestMentionClass": ".list_recommendations_request_mention_class", + "ListRecommendationsRequestSrc": ".list_recommendations_request_src", +} + + +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__ = ["ListRecommendationsRequestMentionClass", "ListRecommendationsRequestSrc"] diff --git a/src/arcmira/entities/recommendations/types/list_recommendations_request_mention_class.py b/src/arcmira/entities/recommendations/types/list_recommendations_request_mention_class.py new file mode 100644 index 0000000..a106d25 --- /dev/null +++ b/src/arcmira/entities/recommendations/types/list_recommendations_request_mention_class.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListRecommendationsRequestMentionClass = typing.Union[ + typing.Literal["ad_read", "endorsement", "mention", "all"], typing.Any +] diff --git a/src/arcmira/entities/recommendations/types/list_recommendations_request_src.py b/src/arcmira/entities/recommendations/types/list_recommendations_request_src.py new file mode 100644 index 0000000..a8ce855 --- /dev/null +++ b/src/arcmira/entities/recommendations/types/list_recommendations_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListRecommendationsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/__init__.py b/src/arcmira/entities/types/__init__.py new file mode 100644 index 0000000..6f4e96f --- /dev/null +++ b/src/arcmira/entities/types/__init__.py @@ -0,0 +1,53 @@ +# 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 .lookup_entities_request_type import LookupEntitiesRequestType + from .momentum_entities_request_src import MomentumEntitiesRequestSrc + from .resolve_entities_request_src import ResolveEntitiesRequestSrc + from .resolve_entities_request_type import ResolveEntitiesRequestType + from .search_entities_request_src import SearchEntitiesRequestSrc + from .search_entities_request_type import SearchEntitiesRequestType +_dynamic_imports: typing.Dict[str, str] = { + "LookupEntitiesRequestType": ".lookup_entities_request_type", + "MomentumEntitiesRequestSrc": ".momentum_entities_request_src", + "ResolveEntitiesRequestSrc": ".resolve_entities_request_src", + "ResolveEntitiesRequestType": ".resolve_entities_request_type", + "SearchEntitiesRequestSrc": ".search_entities_request_src", + "SearchEntitiesRequestType": ".search_entities_request_type", +} + + +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__ = [ + "LookupEntitiesRequestType", + "MomentumEntitiesRequestSrc", + "ResolveEntitiesRequestSrc", + "ResolveEntitiesRequestType", + "SearchEntitiesRequestSrc", + "SearchEntitiesRequestType", +] diff --git a/src/arcmira/entities/types/lookup_entities_request_type.py b/src/arcmira/entities/types/lookup_entities_request_type.py new file mode 100644 index 0000000..9e27c9e --- /dev/null +++ b/src/arcmira/entities/types/lookup_entities_request_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +LookupEntitiesRequestType = typing.Union[ + typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any +] diff --git a/src/arcmira/entities/types/momentum_entities_request_src.py b/src/arcmira/entities/types/momentum_entities_request_src.py new file mode 100644 index 0000000..078afe7 --- /dev/null +++ b/src/arcmira/entities/types/momentum_entities_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MomentumEntitiesRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/resolve_entities_request_src.py b/src/arcmira/entities/types/resolve_entities_request_src.py new file mode 100644 index 0000000..c798156 --- /dev/null +++ b/src/arcmira/entities/types/resolve_entities_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ResolveEntitiesRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/resolve_entities_request_type.py b/src/arcmira/entities/types/resolve_entities_request_type.py new file mode 100644 index 0000000..e6ed8e9 --- /dev/null +++ b/src/arcmira/entities/types/resolve_entities_request_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ResolveEntitiesRequestType = typing.Union[ + typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any +] diff --git a/src/arcmira/entities/types/search_entities_request_src.py b/src/arcmira/entities/types/search_entities_request_src.py new file mode 100644 index 0000000..99be0cc --- /dev/null +++ b/src/arcmira/entities/types/search_entities_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SearchEntitiesRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/search_entities_request_type.py b/src/arcmira/entities/types/search_entities_request_type.py new file mode 100644 index 0000000..8d3c8f9 --- /dev/null +++ b/src/arcmira/entities/types/search_entities_request_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SearchEntitiesRequestType = typing.Union[ + typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any +] diff --git a/src/arcmira/environment.py b/src/arcmira/environment.py new file mode 100644 index 0000000..83725f5 --- /dev/null +++ b/src/arcmira/environment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum + + +class ArcmiraEnvironment(enum.Enum): + DEFAULT = "https://api.arcmira.com" diff --git a/src/arcmira/errors/__init__.py b/src/arcmira/errors/__init__.py new file mode 100644 index 0000000..4218461 --- /dev/null +++ b/src/arcmira/errors/__init__.py @@ -0,0 +1,68 @@ +# 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 .bad_request_error import BadRequestError + from .conflict_error import ConflictError + from .forbidden_error import ForbiddenError + from .internal_server_error import InternalServerError + from .not_found_error import NotFoundError + from .payment_required_error import PaymentRequiredError + from .precondition_failed_error import PreconditionFailedError + from .service_unavailable_error import ServiceUnavailableError + from .too_many_requests_error import TooManyRequestsError + from .unauthorized_error import UnauthorizedError + from .unprocessable_entity_error import UnprocessableEntityError +_dynamic_imports: typing.Dict[str, str] = { + "BadRequestError": ".bad_request_error", + "ConflictError": ".conflict_error", + "ForbiddenError": ".forbidden_error", + "InternalServerError": ".internal_server_error", + "NotFoundError": ".not_found_error", + "PaymentRequiredError": ".payment_required_error", + "PreconditionFailedError": ".precondition_failed_error", + "ServiceUnavailableError": ".service_unavailable_error", + "TooManyRequestsError": ".too_many_requests_error", + "UnauthorizedError": ".unauthorized_error", + "UnprocessableEntityError": ".unprocessable_entity_error", +} + + +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__ = [ + "BadRequestError", + "ConflictError", + "ForbiddenError", + "InternalServerError", + "NotFoundError", + "PaymentRequiredError", + "PreconditionFailedError", + "ServiceUnavailableError", + "TooManyRequestsError", + "UnauthorizedError", + "UnprocessableEntityError", +] diff --git a/src/arcmira/errors/bad_request_error.py b/src/arcmira/errors/bad_request_error.py new file mode 100644 index 0000000..1644a5a --- /dev/null +++ b/src/arcmira/errors/bad_request_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.error import Error + + +class BadRequestError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=400, headers=headers, body=body) diff --git a/src/arcmira/errors/conflict_error.py b/src/arcmira/errors/conflict_error.py new file mode 100644 index 0000000..840a6e7 --- /dev/null +++ b/src/arcmira/errors/conflict_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.error import Error + + +class ConflictError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=409, headers=headers, body=body) diff --git a/src/arcmira/errors/forbidden_error.py b/src/arcmira/errors/forbidden_error.py new file mode 100644 index 0000000..e501c57 --- /dev/null +++ b/src/arcmira/errors/forbidden_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.error import Error + + +class ForbiddenError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=403, headers=headers, body=body) diff --git a/src/arcmira/errors/internal_server_error.py b/src/arcmira/errors/internal_server_error.py new file mode 100644 index 0000000..2c41ca5 --- /dev/null +++ b/src/arcmira/errors/internal_server_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.error import Error + + +class InternalServerError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=500, headers=headers, body=body) diff --git a/src/arcmira/errors/not_found_error.py b/src/arcmira/errors/not_found_error.py new file mode 100644 index 0000000..e7ed720 --- /dev/null +++ b/src/arcmira/errors/not_found_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.error import Error + + +class NotFoundError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=404, headers=headers, body=body) diff --git a/src/arcmira/errors/payment_required_error.py b/src/arcmira/errors/payment_required_error.py new file mode 100644 index 0000000..1fbd073 --- /dev/null +++ b/src/arcmira/errors/payment_required_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.error import Error + + +class PaymentRequiredError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=402, headers=headers, body=body) diff --git a/src/arcmira/errors/precondition_failed_error.py b/src/arcmira/errors/precondition_failed_error.py new file mode 100644 index 0000000..0a8cf48 --- /dev/null +++ b/src/arcmira/errors/precondition_failed_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.correction_seq_mismatch_response import CorrectionSeqMismatchResponse + + +class PreconditionFailedError(ApiError): + def __init__(self, body: CorrectionSeqMismatchResponse, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=412, headers=headers, body=body) diff --git a/src/arcmira/errors/service_unavailable_error.py b/src/arcmira/errors/service_unavailable_error.py new file mode 100644 index 0000000..50e2d01 --- /dev/null +++ b/src/arcmira/errors/service_unavailable_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.error import Error + + +class ServiceUnavailableError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=503, headers=headers, body=body) diff --git a/src/arcmira/errors/too_many_requests_error.py b/src/arcmira/errors/too_many_requests_error.py new file mode 100644 index 0000000..a0743ee --- /dev/null +++ b/src/arcmira/errors/too_many_requests_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.error import Error + + +class TooManyRequestsError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=429, headers=headers, body=body) diff --git a/src/arcmira/errors/unauthorized_error.py b/src/arcmira/errors/unauthorized_error.py new file mode 100644 index 0000000..2345489 --- /dev/null +++ b/src/arcmira/errors/unauthorized_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.error import Error + + +class UnauthorizedError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=401, headers=headers, body=body) diff --git a/src/arcmira/errors/unprocessable_entity_error.py b/src/arcmira/errors/unprocessable_entity_error.py new file mode 100644 index 0000000..31587f1 --- /dev/null +++ b/src/arcmira/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.error import Error + + +class UnprocessableEntityError(ApiError): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=422, headers=headers, body=body) diff --git a/src/arcmira/feedback/__init__.py b/src/arcmira/feedback/__init__.py new file mode 100644 index 0000000..a5427a8 --- /dev/null +++ b/src/arcmira/feedback/__init__.py @@ -0,0 +1,58 @@ +# 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 ( + SubmitFeedbackRequestCorrectionsItem, + SubmitFeedbackRequestCorrectionsItemIssueType, + SubmitFeedbackRequestCorrectionsItemMentionClass, + SubmitFeedbackRequestCorrectionsItemReason, + SubmitFeedbackRequestCorrectionsItemSuggestedChange, + SubmitFeedbackRequestMethod, + SubmitFeedbackRequestType, + ) +_dynamic_imports: typing.Dict[str, str] = { + "SubmitFeedbackRequestCorrectionsItem": ".types", + "SubmitFeedbackRequestCorrectionsItemIssueType": ".types", + "SubmitFeedbackRequestCorrectionsItemMentionClass": ".types", + "SubmitFeedbackRequestCorrectionsItemReason": ".types", + "SubmitFeedbackRequestCorrectionsItemSuggestedChange": ".types", + "SubmitFeedbackRequestMethod": ".types", + "SubmitFeedbackRequestType": ".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__ = [ + "SubmitFeedbackRequestCorrectionsItem", + "SubmitFeedbackRequestCorrectionsItemIssueType", + "SubmitFeedbackRequestCorrectionsItemMentionClass", + "SubmitFeedbackRequestCorrectionsItemReason", + "SubmitFeedbackRequestCorrectionsItemSuggestedChange", + "SubmitFeedbackRequestMethod", + "SubmitFeedbackRequestType", +] diff --git a/src/arcmira/feedback/client.py b/src/arcmira/feedback/client.py new file mode 100644 index 0000000..404dbe5 --- /dev/null +++ b/src/arcmira/feedback/client.py @@ -0,0 +1,285 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.feedback_readback_response import FeedbackReadbackResponse +from ..types.feedback_response import FeedbackResponse +from .raw_client import AsyncRawFeedbackClient, RawFeedbackClient +from .types.submit_feedback_request_corrections_item import SubmitFeedbackRequestCorrectionsItem +from .types.submit_feedback_request_method import SubmitFeedbackRequestMethod +from .types.submit_feedback_request_type import SubmitFeedbackRequestType + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class FeedbackClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawFeedbackClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawFeedbackClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawFeedbackClient + """ + return self._raw_client + + def submit( + self, + *, + type: SubmitFeedbackRequestType, + query: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + endpoint: typing.Optional[str] = OMIT, + method: typing.Optional[SubmitFeedbackRequestMethod] = OMIT, + request_id: typing.Optional[str] = OMIT, + result_url: typing.Optional[str] = OMIT, + source_url: typing.Optional[str] = OMIT, + notes: typing.Optional[str] = OMIT, + corrections: typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> FeedbackResponse: + """ + Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alert rows: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert row id, and every referenced alert row must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with no row to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. + + Parameters + ---------- + type : SubmitFeedbackRequestType + The surface being reviewed. Values: recommendations (/v1/recommendations rows by com_* id; requires a Pro+ plan), channel_sponsors (sponsor entities on a channel; requires a Pro+ plan), mentions (/v1/mentions rows by men_* id), entities_search (/v1/entities/search hits), entities (/v1/entities/lookup and /v1/entities/{id} payloads), channels (/v1/channels/{slug} payloads), monitor_alert (fired alert rows from /v1/monitors/{id}/alerts or /v1/trackers/{id}/alerts; corrections target the alert row id), appearances (person appearance rows), search (rows from /v1/search or /v1/entities/search). + + query : typing.Dict[str, typing.Any] + The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + endpoint : typing.Optional[str] + + method : typing.Optional[SubmitFeedbackRequestMethod] + + request_id : typing.Optional[str] + + result_url : typing.Optional[str] + + source_url : typing.Optional[str] + + notes : typing.Optional[str] + + corrections : typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + FeedbackResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.feedback.submit( + type="recommendations", + query={"key": "value"}, + ) + """ + _response = self._raw_client.submit( + type=type, + query=query, + idempotency_key=idempotency_key, + endpoint=endpoint, + method=method, + request_id=request_id, + result_url=result_url, + source_url=source_url, + notes=notes, + corrections=corrections, + request_options=request_options, + ) + return _response.data + + def get( + self, feedback_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> FeedbackReadbackResponse: + """ + Returns the submission (id, type, query, notes, created_at) plus its per-correction rows, each with a review status in the public vocabulary: pending_review, needs_information, accepted, accepted_with_changes, rejected, withdrawn, applied, reverted (accepted means a reviewer agreed; applied means the change is live in the index). Only the submitting user's keys can read a submission; unknown ids and other users' submissions both return 404 (never 403). + + Parameters + ---------- + feedback_id : str + The feedback submission id POST /v1/feedback returned. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + FeedbackReadbackResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.feedback.get( + feedback_id="feedback_id", + ) + """ + _response = self._raw_client.get(feedback_id, request_options=request_options) + return _response.data + + +class AsyncFeedbackClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawFeedbackClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawFeedbackClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawFeedbackClient + """ + return self._raw_client + + async def submit( + self, + *, + type: SubmitFeedbackRequestType, + query: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + endpoint: typing.Optional[str] = OMIT, + method: typing.Optional[SubmitFeedbackRequestMethod] = OMIT, + request_id: typing.Optional[str] = OMIT, + result_url: typing.Optional[str] = OMIT, + source_url: typing.Optional[str] = OMIT, + notes: typing.Optional[str] = OMIT, + corrections: typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> FeedbackResponse: + """ + Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alert rows: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert row id, and every referenced alert row must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with no row to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. + + Parameters + ---------- + type : SubmitFeedbackRequestType + The surface being reviewed. Values: recommendations (/v1/recommendations rows by com_* id; requires a Pro+ plan), channel_sponsors (sponsor entities on a channel; requires a Pro+ plan), mentions (/v1/mentions rows by men_* id), entities_search (/v1/entities/search hits), entities (/v1/entities/lookup and /v1/entities/{id} payloads), channels (/v1/channels/{slug} payloads), monitor_alert (fired alert rows from /v1/monitors/{id}/alerts or /v1/trackers/{id}/alerts; corrections target the alert row id), appearances (person appearance rows), search (rows from /v1/search or /v1/entities/search). + + query : typing.Dict[str, typing.Any] + The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + endpoint : typing.Optional[str] + + method : typing.Optional[SubmitFeedbackRequestMethod] + + request_id : typing.Optional[str] + + result_url : typing.Optional[str] + + source_url : typing.Optional[str] + + notes : typing.Optional[str] + + corrections : typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + FeedbackResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.feedback.submit( + type="recommendations", + query={"key": "value"}, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.submit( + type=type, + query=query, + idempotency_key=idempotency_key, + endpoint=endpoint, + method=method, + request_id=request_id, + result_url=result_url, + source_url=source_url, + notes=notes, + corrections=corrections, + request_options=request_options, + ) + return _response.data + + async def get( + self, feedback_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> FeedbackReadbackResponse: + """ + Returns the submission (id, type, query, notes, created_at) plus its per-correction rows, each with a review status in the public vocabulary: pending_review, needs_information, accepted, accepted_with_changes, rejected, withdrawn, applied, reverted (accepted means a reviewer agreed; applied means the change is live in the index). Only the submitting user's keys can read a submission; unknown ids and other users' submissions both return 404 (never 403). + + Parameters + ---------- + feedback_id : str + The feedback submission id POST /v1/feedback returned. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + FeedbackReadbackResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.feedback.get( + feedback_id="feedback_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(feedback_id, request_options=request_options) + return _response.data diff --git a/src/arcmira/feedback/raw_client.py b/src/arcmira/feedback/raw_client.py new file mode 100644 index 0000000..95d7274 --- /dev/null +++ b/src/arcmira/feedback/raw_client.py @@ -0,0 +1,602 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.conflict_error import ConflictError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.feedback_readback_response import FeedbackReadbackResponse +from ..types.feedback_response import FeedbackResponse +from .types.submit_feedback_request_corrections_item import SubmitFeedbackRequestCorrectionsItem +from .types.submit_feedback_request_method import SubmitFeedbackRequestMethod +from .types.submit_feedback_request_type import SubmitFeedbackRequestType +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawFeedbackClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def submit( + self, + *, + type: SubmitFeedbackRequestType, + query: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + endpoint: typing.Optional[str] = OMIT, + method: typing.Optional[SubmitFeedbackRequestMethod] = OMIT, + request_id: typing.Optional[str] = OMIT, + result_url: typing.Optional[str] = OMIT, + source_url: typing.Optional[str] = OMIT, + notes: typing.Optional[str] = OMIT, + corrections: typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[FeedbackResponse]: + """ + Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alert rows: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert row id, and every referenced alert row must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with no row to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. + + Parameters + ---------- + type : SubmitFeedbackRequestType + The surface being reviewed. Values: recommendations (/v1/recommendations rows by com_* id; requires a Pro+ plan), channel_sponsors (sponsor entities on a channel; requires a Pro+ plan), mentions (/v1/mentions rows by men_* id), entities_search (/v1/entities/search hits), entities (/v1/entities/lookup and /v1/entities/{id} payloads), channels (/v1/channels/{slug} payloads), monitor_alert (fired alert rows from /v1/monitors/{id}/alerts or /v1/trackers/{id}/alerts; corrections target the alert row id), appearances (person appearance rows), search (rows from /v1/search or /v1/entities/search). + + query : typing.Dict[str, typing.Any] + The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + endpoint : typing.Optional[str] + + method : typing.Optional[SubmitFeedbackRequestMethod] + + request_id : typing.Optional[str] + + result_url : typing.Optional[str] + + source_url : typing.Optional[str] + + notes : typing.Optional[str] + + corrections : typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[FeedbackResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/feedback", + method="POST", + json={ + "type": type, + "query": query, + "endpoint": endpoint, + "method": method, + "request_id": request_id, + "result_url": result_url, + "source_url": source_url, + "notes": notes, + "corrections": convert_and_respect_annotation_metadata( + object_=corrections, + annotation=typing.Sequence[SubmitFeedbackRequestCorrectionsItem], + direction="write", + ), + }, + 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( + FeedbackResponse, + parse_obj_as( + type_=FeedbackResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, feedback_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[FeedbackReadbackResponse]: + """ + Returns the submission (id, type, query, notes, created_at) plus its per-correction rows, each with a review status in the public vocabulary: pending_review, needs_information, accepted, accepted_with_changes, rejected, withdrawn, applied, reverted (accepted means a reviewer agreed; applied means the change is live in the index). Only the submitting user's keys can read a submission; unknown ids and other users' submissions both return 404 (never 403). + + Parameters + ---------- + feedback_id : str + The feedback submission id POST /v1/feedback returned. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[FeedbackReadbackResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/feedback/{encode_path_param(feedback_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + FeedbackReadbackResponse, + parse_obj_as( + type_=FeedbackReadbackResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawFeedbackClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def submit( + self, + *, + type: SubmitFeedbackRequestType, + query: typing.Dict[str, typing.Any], + idempotency_key: typing.Optional[str] = None, + endpoint: typing.Optional[str] = OMIT, + method: typing.Optional[SubmitFeedbackRequestMethod] = OMIT, + request_id: typing.Optional[str] = OMIT, + result_url: typing.Optional[str] = OMIT, + source_url: typing.Optional[str] = OMIT, + notes: typing.Optional[str] = OMIT, + corrections: typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[FeedbackResponse]: + """ + Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alert rows: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert row id, and every referenced alert row must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with no row to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. + + Parameters + ---------- + type : SubmitFeedbackRequestType + The surface being reviewed. Values: recommendations (/v1/recommendations rows by com_* id; requires a Pro+ plan), channel_sponsors (sponsor entities on a channel; requires a Pro+ plan), mentions (/v1/mentions rows by men_* id), entities_search (/v1/entities/search hits), entities (/v1/entities/lookup and /v1/entities/{id} payloads), channels (/v1/channels/{slug} payloads), monitor_alert (fired alert rows from /v1/monitors/{id}/alerts or /v1/trackers/{id}/alerts; corrections target the alert row id), appearances (person appearance rows), search (rows from /v1/search or /v1/entities/search). + + query : typing.Dict[str, typing.Any] + The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + endpoint : typing.Optional[str] + + method : typing.Optional[SubmitFeedbackRequestMethod] + + request_id : typing.Optional[str] + + result_url : typing.Optional[str] + + source_url : typing.Optional[str] + + notes : typing.Optional[str] + + corrections : typing.Optional[typing.Sequence[SubmitFeedbackRequestCorrectionsItem]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[FeedbackResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/feedback", + method="POST", + json={ + "type": type, + "query": query, + "endpoint": endpoint, + "method": method, + "request_id": request_id, + "result_url": result_url, + "source_url": source_url, + "notes": notes, + "corrections": convert_and_respect_annotation_metadata( + object_=corrections, + annotation=typing.Sequence[SubmitFeedbackRequestCorrectionsItem], + direction="write", + ), + }, + 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( + FeedbackResponse, + parse_obj_as( + type_=FeedbackResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, feedback_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[FeedbackReadbackResponse]: + """ + Returns the submission (id, type, query, notes, created_at) plus its per-correction rows, each with a review status in the public vocabulary: pending_review, needs_information, accepted, accepted_with_changes, rejected, withdrawn, applied, reverted (accepted means a reviewer agreed; applied means the change is live in the index). Only the submitting user's keys can read a submission; unknown ids and other users' submissions both return 404 (never 403). + + Parameters + ---------- + feedback_id : str + The feedback submission id POST /v1/feedback returned. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[FeedbackReadbackResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/feedback/{encode_path_param(feedback_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + FeedbackReadbackResponse, + parse_obj_as( + type_=FeedbackReadbackResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/feedback/types/__init__.py b/src/arcmira/feedback/types/__init__.py new file mode 100644 index 0000000..9f745e3 --- /dev/null +++ b/src/arcmira/feedback/types/__init__.py @@ -0,0 +1,58 @@ +# 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 .submit_feedback_request_corrections_item import SubmitFeedbackRequestCorrectionsItem + from .submit_feedback_request_corrections_item_issue_type import SubmitFeedbackRequestCorrectionsItemIssueType + from .submit_feedback_request_corrections_item_mention_class import SubmitFeedbackRequestCorrectionsItemMentionClass + from .submit_feedback_request_corrections_item_reason import SubmitFeedbackRequestCorrectionsItemReason + from .submit_feedback_request_corrections_item_suggested_change import ( + SubmitFeedbackRequestCorrectionsItemSuggestedChange, + ) + from .submit_feedback_request_method import SubmitFeedbackRequestMethod + from .submit_feedback_request_type import SubmitFeedbackRequestType +_dynamic_imports: typing.Dict[str, str] = { + "SubmitFeedbackRequestCorrectionsItem": ".submit_feedback_request_corrections_item", + "SubmitFeedbackRequestCorrectionsItemIssueType": ".submit_feedback_request_corrections_item_issue_type", + "SubmitFeedbackRequestCorrectionsItemMentionClass": ".submit_feedback_request_corrections_item_mention_class", + "SubmitFeedbackRequestCorrectionsItemReason": ".submit_feedback_request_corrections_item_reason", + "SubmitFeedbackRequestCorrectionsItemSuggestedChange": ".submit_feedback_request_corrections_item_suggested_change", + "SubmitFeedbackRequestMethod": ".submit_feedback_request_method", + "SubmitFeedbackRequestType": ".submit_feedback_request_type", +} + + +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__ = [ + "SubmitFeedbackRequestCorrectionsItem", + "SubmitFeedbackRequestCorrectionsItemIssueType", + "SubmitFeedbackRequestCorrectionsItemMentionClass", + "SubmitFeedbackRequestCorrectionsItemReason", + "SubmitFeedbackRequestCorrectionsItemSuggestedChange", + "SubmitFeedbackRequestMethod", + "SubmitFeedbackRequestType", +] diff --git a/src/arcmira/feedback/types/submit_feedback_request_corrections_item.py b/src/arcmira/feedback/types/submit_feedback_request_corrections_item.py new file mode 100644 index 0000000..9c9254e --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_corrections_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 .submit_feedback_request_corrections_item_issue_type import SubmitFeedbackRequestCorrectionsItemIssueType +from .submit_feedback_request_corrections_item_mention_class import SubmitFeedbackRequestCorrectionsItemMentionClass +from .submit_feedback_request_corrections_item_reason import SubmitFeedbackRequestCorrectionsItemReason +from .submit_feedback_request_corrections_item_suggested_change import ( + SubmitFeedbackRequestCorrectionsItemSuggestedChange, +) + + +class SubmitFeedbackRequestCorrectionsItem(UniversalBaseModel): + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id of the row being corrected, from the response you received: men_* (mentions, appearances), com_* (recommendations), ent_* (entities, sponsors), or the alert row id (monitor_alert). Omit for missed_alert and missing_result corrections, which have no row to target. + """ + + mention_class: typing.Optional[SubmitFeedbackRequestCorrectionsItemMentionClass] = None + reason: typing.Optional[SubmitFeedbackRequestCorrectionsItemReason] = None + issue_type: typing.Optional[SubmitFeedbackRequestCorrectionsItemIssueType] = pydantic.Field(default=None) + """ + Issue classification for the correction. Entity-family values: wrong_entity_type (right entity, wrong type), wrong_entity (the row points at the wrong canonical entity), duplicate_entity (results split across variants of the same entity), merge_suggestion (propose the canonical merge for split variants), missing_result (a result you know should exist is absent), stale_metadata (name/website/channel metadata is outdated), wrong_classification (class-level error on a commercial row), bad_ranking (duplicates or aliases ranking above the canonical entity). monitor_alert values: false_positive_alert (the alert should not have fired), wrong_media (fired against the wrong video), wrong_timestamp (fired at the wrong position in the video), duplicate_alert (the same occurrence fired more than once), missed_alert (an expectation: an alert that should have fired but did not; no row to target), delivery_issue (the delivery itself was wrong: wrong channel, not received). appearances values: person_not_present (the person does not appear in the media), wrong_person (the appearance is attributed to the wrong person), wrong_appearance_role (right person, wrong role, e.g. guest vs host). other (escape hatch; detail in notes). + """ + + suggested_change: typing.Optional[SubmitFeedbackRequestCorrectionsItemSuggestedChange] = pydantic.Field( + default=None + ) + """ + Your concrete proposed fix, shaped by issue_type: merge_suggestion → MergeSuggestionChange, wrong_entity/wrong_person → WrongEntityChange, wrong_entity_type → WrongEntityTypeChange, missing_result → MissingResultChange, wrong_classification → WrongClassificationChange, stale_metadata → StaleMetadataChange, bad_ranking → BadRankingChange, missed_alert → MissedAlertChange, delivery_issue → DeliveryIssueChange. Unknown keys are accepted and logged verbatim; only non-object values are rejected. Omit suggested_change entirely when you do not have a concrete fix. + """ + + notes: 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/arcmira/feedback/types/submit_feedback_request_corrections_item_issue_type.py b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_issue_type.py new file mode 100644 index 0000000..7f5c0ef --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_issue_type.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubmitFeedbackRequestCorrectionsItemIssueType = typing.Union[ + typing.Literal[ + "wrong_entity_type", + "wrong_entity", + "duplicate_entity", + "merge_suggestion", + "missing_result", + "stale_metadata", + "wrong_classification", + "bad_ranking", + "false_positive_alert", + "wrong_media", + "wrong_timestamp", + "duplicate_alert", + "missed_alert", + "delivery_issue", + "person_not_present", + "wrong_person", + "wrong_appearance_role", + "other", + ], + typing.Any, +] diff --git a/src/arcmira/feedback/types/submit_feedback_request_corrections_item_mention_class.py b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_mention_class.py new file mode 100644 index 0000000..977460b --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_mention_class.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubmitFeedbackRequestCorrectionsItemMentionClass = typing.Union[ + typing.Literal["ad_read", "endorsement", "mention"], typing.Any +] diff --git a/src/arcmira/feedback/types/submit_feedback_request_corrections_item_reason.py b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_reason.py new file mode 100644 index 0000000..fc35783 --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_reason.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubmitFeedbackRequestCorrectionsItemReason = typing.Union[ + typing.Literal[ + "false_positive_ad_read", + "false_positive_endorsement", + "missed_ad_read", + "missed_endorsement", + "wrong_classification", + "wrong_entity", + "other", + ], + typing.Any, +] diff --git a/src/arcmira/feedback/types/submit_feedback_request_corrections_item_suggested_change.py b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_suggested_change.py new file mode 100644 index 0000000..7189313 --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_corrections_item_suggested_change.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...types.bad_ranking_change import BadRankingChange +from ...types.delivery_issue_change import DeliveryIssueChange +from ...types.freeform_suggested_change import FreeformSuggestedChange +from ...types.merge_suggestion_change import MergeSuggestionChange +from ...types.missed_alert_change import MissedAlertChange +from ...types.missing_result_change import MissingResultChange +from ...types.stale_metadata_change import StaleMetadataChange +from ...types.wrong_classification_change import WrongClassificationChange +from ...types.wrong_entity_change import WrongEntityChange +from ...types.wrong_entity_type_change import WrongEntityTypeChange + +SubmitFeedbackRequestCorrectionsItemSuggestedChange = typing.Union[ + MergeSuggestionChange, + WrongEntityChange, + WrongEntityTypeChange, + MissingResultChange, + WrongClassificationChange, + StaleMetadataChange, + BadRankingChange, + MissedAlertChange, + DeliveryIssueChange, + FreeformSuggestedChange, +] diff --git a/src/arcmira/feedback/types/submit_feedback_request_method.py b/src/arcmira/feedback/types/submit_feedback_request_method.py new file mode 100644 index 0000000..05b883d --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_method.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubmitFeedbackRequestMethod = typing.Union[typing.Literal["GET", "POST", "PATCH", "PUT", "DELETE"], typing.Any] diff --git a/src/arcmira/feedback/types/submit_feedback_request_type.py b/src/arcmira/feedback/types/submit_feedback_request_type.py new file mode 100644 index 0000000..1ae5933 --- /dev/null +++ b/src/arcmira/feedback/types/submit_feedback_request_type.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubmitFeedbackRequestType = typing.Union[ + typing.Literal[ + "recommendations", + "channel_sponsors", + "mentions", + "entities_search", + "entities", + "channels", + "monitor_alert", + "appearances", + "search", + ], + typing.Any, +] diff --git a/src/arcmira/health/__init__.py b/src/arcmira/health/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/health/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/health/client.py b/src/arcmira/health/client.py new file mode 100644 index 0000000..f4e9fbf --- /dev/null +++ b/src/arcmira/health/client.py @@ -0,0 +1,96 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.health_response import HealthResponse +from .raw_client import AsyncRawHealthClient, RawHealthClient + + +class HealthClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawHealthClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawHealthClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawHealthClient + """ + return self._raw_client + + def check(self, *, request_options: typing.Optional[RequestOptions] = None) -> HealthResponse: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HealthResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.health.check() + """ + _response = self._raw_client.check(request_options=request_options) + return _response.data + + +class AsyncHealthClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawHealthClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawHealthClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawHealthClient + """ + return self._raw_client + + async def check(self, *, request_options: typing.Optional[RequestOptions] = None) -> HealthResponse: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HealthResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.health.check() + + + asyncio.run(main()) + """ + _response = await self._raw_client.check(request_options=request_options) + return _response.data diff --git a/src/arcmira/health/raw_client.py b/src/arcmira/health/raw_client.py new file mode 100644 index 0000000..464724e --- /dev/null +++ b/src/arcmira/health/raw_client.py @@ -0,0 +1,144 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.internal_server_error import InternalServerError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..types.error import Error +from ..types.health_response import HealthResponse +from pydantic import ValidationError + + +class RawHealthClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def check(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[HealthResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[HealthResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/health", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + HealthResponse, + parse_obj_as( + type_=HealthResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawHealthClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def check( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[HealthResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[HealthResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/health", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + HealthResponse, + parse_obj_as( + type_=HealthResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/me/__init__.py b/src/arcmira/me/__init__.py new file mode 100644 index 0000000..08e68f7 --- /dev/null +++ b/src/arcmira/me/__init__.py @@ -0,0 +1,37 @@ +# 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 UpdateSettingsMeRequestTranscripts, UpdateSettingsMeRequestTranscriptsQuality +_dynamic_imports: typing.Dict[str, str] = { + "UpdateSettingsMeRequestTranscripts": ".types", + "UpdateSettingsMeRequestTranscriptsQuality": ".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__ = ["UpdateSettingsMeRequestTranscripts", "UpdateSettingsMeRequestTranscriptsQuality"] diff --git a/src/arcmira/me/client.py b/src/arcmira/me/client.py new file mode 100644 index 0000000..264e0ed --- /dev/null +++ b/src/arcmira/me/client.py @@ -0,0 +1,187 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.me_response import MeResponse +from ..types.me_settings_response import MeSettingsResponse +from .raw_client import AsyncRawMeClient, RawMeClient +from .types.update_settings_me_request_transcripts import UpdateSettingsMeRequestTranscripts + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class MeClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMeClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMeClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMeClient + """ + return self._raw_client + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> MeResponse: + """ + Available even when the account has exhausted its usage allowance. Returns the credential making the request (key_id, key_label, credential_kind), the masked account email, the tier, scopes, rate limit, row usage with period_resets_at, and account settings. settings.transcripts is what a transcript request that names no parameter of its own receives: every key of the account resolves against it. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MeResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.me.get() + """ + _response = self._raw_client.get(request_options=request_options) + return _response.data + + def update_settings( + self, + *, + transcripts: UpdateSettingsMeRequestTranscripts, + request_options: typing.Optional[RequestOptions] = None, + ) -> MeSettingsResponse: + """ + Sets the account defaults every key of the account resolves against. Send only the fields you are changing; an omitted field keeps the value it has. Resolution order for every transcript request is the explicit parameter, then these settings, then the platform default, so a default never overrides a parameter the caller sent. Defaults live on the account, never on a key: two keys of one account answer the same request the same way. The response echoes the resolved settings. + + Parameters + ---------- + transcripts : UpdateSettingsMeRequestTranscripts + Fields to change. An omitted field keeps the value the account already carries. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MeSettingsResponse + Settings updated + + Examples + -------- + from arcmira import Arcmira + from arcmira.me import UpdateSettingsMeRequestTranscripts + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.me.update_settings( + transcripts=UpdateSettingsMeRequestTranscripts(), + ) + """ + _response = self._raw_client.update_settings(transcripts=transcripts, request_options=request_options) + return _response.data + + +class AsyncMeClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMeClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMeClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMeClient + """ + return self._raw_client + + async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> MeResponse: + """ + Available even when the account has exhausted its usage allowance. Returns the credential making the request (key_id, key_label, credential_kind), the masked account email, the tier, scopes, rate limit, row usage with period_resets_at, and account settings. settings.transcripts is what a transcript request that names no parameter of its own receives: every key of the account resolves against it. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MeResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.me.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_options=request_options) + return _response.data + + async def update_settings( + self, + *, + transcripts: UpdateSettingsMeRequestTranscripts, + request_options: typing.Optional[RequestOptions] = None, + ) -> MeSettingsResponse: + """ + Sets the account defaults every key of the account resolves against. Send only the fields you are changing; an omitted field keeps the value it has. Resolution order for every transcript request is the explicit parameter, then these settings, then the platform default, so a default never overrides a parameter the caller sent. Defaults live on the account, never on a key: two keys of one account answer the same request the same way. The response echoes the resolved settings. + + Parameters + ---------- + transcripts : UpdateSettingsMeRequestTranscripts + Fields to change. An omitted field keeps the value the account already carries. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MeSettingsResponse + Settings updated + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + from arcmira.me import UpdateSettingsMeRequestTranscripts + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.me.update_settings( + transcripts=UpdateSettingsMeRequestTranscripts(), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update_settings(transcripts=transcripts, request_options=request_options) + return _response.data diff --git a/src/arcmira/me/raw_client.py b/src/arcmira/me/raw_client.py new file mode 100644 index 0000000..551ee67 --- /dev/null +++ b/src/arcmira/me/raw_client.py @@ -0,0 +1,486 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.me_response import MeResponse +from ..types.me_settings_response import MeSettingsResponse +from .types.update_settings_me_request_transcripts import UpdateSettingsMeRequestTranscripts +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawMeClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[MeResponse]: + """ + Available even when the account has exhausted its usage allowance. Returns the credential making the request (key_id, key_label, credential_kind), the masked account email, the tier, scopes, rate limit, row usage with period_resets_at, and account settings. settings.transcripts is what a transcript request that names no parameter of its own receives: every key of the account resolves against it. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MeResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/me", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MeResponse, + parse_obj_as( + type_=MeResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 update_settings( + self, + *, + transcripts: UpdateSettingsMeRequestTranscripts, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MeSettingsResponse]: + """ + Sets the account defaults every key of the account resolves against. Send only the fields you are changing; an omitted field keeps the value it has. Resolution order for every transcript request is the explicit parameter, then these settings, then the platform default, so a default never overrides a parameter the caller sent. Defaults live on the account, never on a key: two keys of one account answer the same request the same way. The response echoes the resolved settings. + + Parameters + ---------- + transcripts : UpdateSettingsMeRequestTranscripts + Fields to change. An omitted field keeps the value the account already carries. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MeSettingsResponse] + Settings updated + """ + _response = self._client_wrapper.httpx_client.request( + "v1/me/settings", + method="PATCH", + json={ + "transcripts": convert_and_respect_annotation_metadata( + object_=transcripts, annotation=UpdateSettingsMeRequestTranscripts, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MeSettingsResponse, + parse_obj_as( + type_=MeSettingsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawMeClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[MeResponse]: + """ + Available even when the account has exhausted its usage allowance. Returns the credential making the request (key_id, key_label, credential_kind), the masked account email, the tier, scopes, rate limit, row usage with period_resets_at, and account settings. settings.transcripts is what a transcript request that names no parameter of its own receives: every key of the account resolves against it. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MeResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/me", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MeResponse, + parse_obj_as( + type_=MeResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 update_settings( + self, + *, + transcripts: UpdateSettingsMeRequestTranscripts, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MeSettingsResponse]: + """ + Sets the account defaults every key of the account resolves against. Send only the fields you are changing; an omitted field keeps the value it has. Resolution order for every transcript request is the explicit parameter, then these settings, then the platform default, so a default never overrides a parameter the caller sent. Defaults live on the account, never on a key: two keys of one account answer the same request the same way. The response echoes the resolved settings. + + Parameters + ---------- + transcripts : UpdateSettingsMeRequestTranscripts + Fields to change. An omitted field keeps the value the account already carries. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MeSettingsResponse] + Settings updated + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/me/settings", + method="PATCH", + json={ + "transcripts": convert_and_respect_annotation_metadata( + object_=transcripts, annotation=UpdateSettingsMeRequestTranscripts, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MeSettingsResponse, + parse_obj_as( + type_=MeSettingsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/me/types/__init__.py b/src/arcmira/me/types/__init__.py new file mode 100644 index 0000000..9a77ded --- /dev/null +++ b/src/arcmira/me/types/__init__.py @@ -0,0 +1,38 @@ +# 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 .update_settings_me_request_transcripts import UpdateSettingsMeRequestTranscripts + from .update_settings_me_request_transcripts_quality import UpdateSettingsMeRequestTranscriptsQuality +_dynamic_imports: typing.Dict[str, str] = { + "UpdateSettingsMeRequestTranscripts": ".update_settings_me_request_transcripts", + "UpdateSettingsMeRequestTranscriptsQuality": ".update_settings_me_request_transcripts_quality", +} + + +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__ = ["UpdateSettingsMeRequestTranscripts", "UpdateSettingsMeRequestTranscriptsQuality"] diff --git a/src/arcmira/me/types/update_settings_me_request_transcripts.py b/src/arcmira/me/types/update_settings_me_request_transcripts.py new file mode 100644 index 0000000..8229ebd --- /dev/null +++ b/src/arcmira/me/types/update_settings_me_request_transcripts.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 .update_settings_me_request_transcripts_quality import UpdateSettingsMeRequestTranscriptsQuality + + +class UpdateSettingsMeRequestTranscripts(UniversalBaseModel): + """ + Fields to change. An omitted field keeps the value the account already carries. + """ + + quality: typing.Optional[UpdateSettingsMeRequestTranscriptsQuality] = pydantic.Field(default=None) + """ + Default transcript quality for this account: captions or premium. premium reads require an existing purchase. Owned transcripts remain readable after a plan downgrade; new purchases require an eligible plan. + """ + + language: typing.Optional[str] = pydantic.Field(default=None) + """ + Default comma-separated caption language priority list, tried in order (e.g. "de,en"), at most 5 codes. Use asr for the first automatic track and asr- for a specific one. + """ + + timestamps: typing.Optional[bool] = pydantic.Field(default=None) + """ + Default for the timestamps parameter. false makes paragraphs[] the default body shape instead of lines[]. + """ + + 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/arcmira/me/types/update_settings_me_request_transcripts_quality.py b/src/arcmira/me/types/update_settings_me_request_transcripts_quality.py new file mode 100644 index 0000000..7859ae2 --- /dev/null +++ b/src/arcmira/me/types/update_settings_me_request_transcripts_quality.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +UpdateSettingsMeRequestTranscriptsQuality = typing.Union[typing.Literal["captions", "premium"], typing.Any] diff --git a/src/arcmira/mentions/__init__.py b/src/arcmira/mentions/__init__.py new file mode 100644 index 0000000..96efbee --- /dev/null +++ b/src/arcmira/mentions/__init__.py @@ -0,0 +1,55 @@ +# 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 ( + CountMentionsRequestMode, + CountMentionsRequestSrc, + ListMentionsRequestDetails, + ListMentionsRequestEntityType, + ListMentionsRequestSentiment, + ListMentionsRequestSrc, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CountMentionsRequestMode": ".types", + "CountMentionsRequestSrc": ".types", + "ListMentionsRequestDetails": ".types", + "ListMentionsRequestEntityType": ".types", + "ListMentionsRequestSentiment": ".types", + "ListMentionsRequestSrc": ".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__ = [ + "CountMentionsRequestMode", + "CountMentionsRequestSrc", + "ListMentionsRequestDetails", + "ListMentionsRequestEntityType", + "ListMentionsRequestSentiment", + "ListMentionsRequestSrc", +] diff --git a/src/arcmira/mentions/client.py b/src/arcmira/mentions/client.py new file mode 100644 index 0000000..a91ecaa --- /dev/null +++ b/src/arcmira/mentions/client.py @@ -0,0 +1,408 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pagination import AsyncPager, SyncPager +from ..core.request_options import RequestOptions +from ..types.mention import Mention +from ..types.mention_counts_response import MentionCountsResponse +from ..types.mention_list_response import MentionListResponse +from .raw_client import AsyncRawMentionsClient, RawMentionsClient +from .types.count_mentions_request_mode import CountMentionsRequestMode +from .types.count_mentions_request_src import CountMentionsRequestSrc +from .types.list_mentions_request_details import ListMentionsRequestDetails +from .types.list_mentions_request_entity_type import ListMentionsRequestEntityType +from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment +from .types.list_mentions_request_src import ListMentionsRequestSrc + + +class MentionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMentionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMentionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMentionsClient + """ + return self._raw_client + + def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListMentionsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListMentionsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Mention, MentionListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.mentions.list() + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + limit=limit, + cursor=cursor, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + + def count( + self, + *, + channel_ids: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + video_ids: typing.Optional[str] = None, + entity_types: typing.Optional[str] = None, + mode: typing.Optional[CountMentionsRequestMode] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[CountMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MentionCountsResponse: + """ + A small ranked table of entity and channel counts, all-time unless published_after is set. Pass channel_ids for what shows talk about and entity_types to match the question (topic for subjects, person for guests, organization,product for brands). Pass video_ids with one id from GET /v1/channels/{channel_id}/videos for what a single episode mentions. Two or more channel_ids also return shared, the entities on more than one of them ranked by the smallest per-channel count, which is true overlap. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per table row returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities. + + video_ids : typing.Optional[str] + Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos. + + entity_types : typing.Optional[str] + Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate. + + mode : typing.Optional[CountMentionsRequestMode] + mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions. + + published_after : typing.Optional[str] + ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + limit : typing.Optional[int] + Rows in the ranked table, 1 to 40. Default 20. + + src : typing.Optional[CountMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MentionCountsResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.mentions.count() + """ + _response = self._raw_client.count( + channel_ids=channel_ids, + entity_ids=entity_ids, + video_ids=video_ids, + entity_types=entity_types, + mode=mode, + published_after=published_after, + published_before=published_before, + limit=limit, + src=src, + request_options=request_options, + ) + return _response.data + + +class AsyncMentionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMentionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMentionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMentionsClient + """ + return self._raw_client + + async def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListMentionsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListMentionsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Mention, MentionListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.mentions.list() + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + limit=limit, + cursor=cursor, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + + async def count( + self, + *, + channel_ids: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + video_ids: typing.Optional[str] = None, + entity_types: typing.Optional[str] = None, + mode: typing.Optional[CountMentionsRequestMode] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[CountMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MentionCountsResponse: + """ + A small ranked table of entity and channel counts, all-time unless published_after is set. Pass channel_ids for what shows talk about and entity_types to match the question (topic for subjects, person for guests, organization,product for brands). Pass video_ids with one id from GET /v1/channels/{channel_id}/videos for what a single episode mentions. Two or more channel_ids also return shared, the entities on more than one of them ranked by the smallest per-channel count, which is true overlap. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per table row returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities. + + video_ids : typing.Optional[str] + Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos. + + entity_types : typing.Optional[str] + Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate. + + mode : typing.Optional[CountMentionsRequestMode] + mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions. + + published_after : typing.Optional[str] + ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + limit : typing.Optional[int] + Rows in the ranked table, 1 to 40. Default 20. + + src : typing.Optional[CountMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MentionCountsResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.mentions.count() + + + asyncio.run(main()) + """ + _response = await self._raw_client.count( + channel_ids=channel_ids, + entity_ids=entity_ids, + video_ids=video_ids, + entity_types=entity_types, + mode=mode, + published_after=published_after, + published_before=published_before, + limit=limit, + src=src, + request_options=request_options, + ) + return _response.data diff --git a/src/arcmira/mentions/raw_client.py b/src/arcmira/mentions/raw_client.py new file mode 100644 index 0000000..f6d703e --- /dev/null +++ b/src/arcmira/mentions/raw_client.py @@ -0,0 +1,773 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.mention import Mention +from ..types.mention_counts_response import MentionCountsResponse +from ..types.mention_list_response import MentionListResponse +from .types.count_mentions_request_mode import CountMentionsRequestMode +from .types.count_mentions_request_src import CountMentionsRequestSrc +from .types.list_mentions_request_details import ListMentionsRequestDetails +from .types.list_mentions_request_entity_type import ListMentionsRequestEntityType +from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment +from .types.list_mentions_request_src import ListMentionsRequestSrc +from pydantic import ValidationError + + +class RawMentionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListMentionsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListMentionsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Mention, MentionListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/mentions", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "entity_id": entity_id, + "entity_name": entity_name, + "entity_type": entity_type, + "channel_id": channel_id, + "channel_name": channel_name, + "q": q, + "sentiment": sentiment, + "is_appearance": is_appearance, + "date_from": date_from, + "date_to": date_to, + "details": details, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + MentionListResponse, + parse_obj_as( + type_=MentionListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + limit=limit, + cursor=_parsed_next, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 count( + self, + *, + channel_ids: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + video_ids: typing.Optional[str] = None, + entity_types: typing.Optional[str] = None, + mode: typing.Optional[CountMentionsRequestMode] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[CountMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MentionCountsResponse]: + """ + A small ranked table of entity and channel counts, all-time unless published_after is set. Pass channel_ids for what shows talk about and entity_types to match the question (topic for subjects, person for guests, organization,product for brands). Pass video_ids with one id from GET /v1/channels/{channel_id}/videos for what a single episode mentions. Two or more channel_ids also return shared, the entities on more than one of them ranked by the smallest per-channel count, which is true overlap. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per table row returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities. + + video_ids : typing.Optional[str] + Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos. + + entity_types : typing.Optional[str] + Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate. + + mode : typing.Optional[CountMentionsRequestMode] + mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions. + + published_after : typing.Optional[str] + ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + limit : typing.Optional[int] + Rows in the ranked table, 1 to 40. Default 20. + + src : typing.Optional[CountMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MentionCountsResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/mentions/counts", + method="GET", + params={ + "channel_ids": channel_ids, + "entity_ids": entity_ids, + "video_ids": video_ids, + "entity_types": entity_types, + "mode": mode, + "published_after": published_after, + "published_before": published_before, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MentionCountsResponse, + parse_obj_as( + type_=MentionCountsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawMentionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListMentionsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + q: typing.Optional[str] = None, + sentiment: typing.Optional[ListMentionsRequestSentiment] = None, + is_appearance: typing.Optional[bool] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + details: typing.Optional[ListMentionsRequestDetails] = None, + src: typing.Optional[ListMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Mention, MentionListResponse]: + """ + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListMentionsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + q : typing.Optional[str] + + sentiment : typing.Optional[ListMentionsRequestSentiment] + + is_appearance : typing.Optional[bool] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + details : typing.Optional[ListMentionsRequestDetails] + + src : typing.Optional[ListMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Mention, MentionListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/mentions", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "entity_id": entity_id, + "entity_name": entity_name, + "entity_type": entity_type, + "channel_id": channel_id, + "channel_name": channel_name, + "q": q, + "sentiment": sentiment, + "is_appearance": is_appearance, + "date_from": date_from, + "date_to": date_to, + "details": details, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + MentionListResponse, + parse_obj_as( + type_=MentionListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + limit=limit, + cursor=_parsed_next, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + q=q, + sentiment=sentiment, + is_appearance=is_appearance, + date_from=date_from, + date_to=date_to, + details=details, + src=src, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 count( + self, + *, + channel_ids: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + video_ids: typing.Optional[str] = None, + entity_types: typing.Optional[str] = None, + mode: typing.Optional[CountMentionsRequestMode] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[CountMentionsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MentionCountsResponse]: + """ + A small ranked table of entity and channel counts, all-time unless published_after is set. Pass channel_ids for what shows talk about and entity_types to match the question (topic for subjects, person for guests, organization,product for brands). Pass video_ids with one id from GET /v1/channels/{channel_id}/videos for what a single episode mentions. Two or more channel_ids also return shared, the entities on more than one of them ranked by the smallest per-channel count, which is true overlap. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per table row returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Two or more also return shared, the entities on more than one of them. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 20. Counts only these entities. + + video_ids : typing.Optional[str] + Comma-separated 11-character YouTube video ids, at most 20. Counts only these episodes; take the ids from GET /v1/channels/{channel_id}/videos. + + entity_types : typing.Optional[str] + Comma-separated entity types to count: person, organization, product, topic, channel. Subjects are topic; guests are person; brands are organization,product. Omit and organizations dominate. + + mode : typing.Optional[CountMentionsRequestMode] + mentions counts talk about an entity; appearances counts a person being present; both counts either. Default mentions. + + published_after : typing.Optional[str] + ISO date. Counts are all-time without it. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + limit : typing.Optional[int] + Rows in the ranked table, 1 to 40. Default 20. + + src : typing.Optional[CountMentionsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MentionCountsResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/mentions/counts", + method="GET", + params={ + "channel_ids": channel_ids, + "entity_ids": entity_ids, + "video_ids": video_ids, + "entity_types": entity_types, + "mode": mode, + "published_after": published_after, + "published_before": published_before, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MentionCountsResponse, + parse_obj_as( + type_=MentionCountsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/mentions/types/__init__.py b/src/arcmira/mentions/types/__init__.py new file mode 100644 index 0000000..611abaf --- /dev/null +++ b/src/arcmira/mentions/types/__init__.py @@ -0,0 +1,53 @@ +# 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 .count_mentions_request_mode import CountMentionsRequestMode + from .count_mentions_request_src import CountMentionsRequestSrc + from .list_mentions_request_details import ListMentionsRequestDetails + from .list_mentions_request_entity_type import ListMentionsRequestEntityType + from .list_mentions_request_sentiment import ListMentionsRequestSentiment + from .list_mentions_request_src import ListMentionsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "CountMentionsRequestMode": ".count_mentions_request_mode", + "CountMentionsRequestSrc": ".count_mentions_request_src", + "ListMentionsRequestDetails": ".list_mentions_request_details", + "ListMentionsRequestEntityType": ".list_mentions_request_entity_type", + "ListMentionsRequestSentiment": ".list_mentions_request_sentiment", + "ListMentionsRequestSrc": ".list_mentions_request_src", +} + + +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__ = [ + "CountMentionsRequestMode", + "CountMentionsRequestSrc", + "ListMentionsRequestDetails", + "ListMentionsRequestEntityType", + "ListMentionsRequestSentiment", + "ListMentionsRequestSrc", +] diff --git a/src/arcmira/mentions/types/count_mentions_request_mode.py b/src/arcmira/mentions/types/count_mentions_request_mode.py new file mode 100644 index 0000000..cb2e3f2 --- /dev/null +++ b/src/arcmira/mentions/types/count_mentions_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CountMentionsRequestMode = typing.Union[typing.Literal["mentions", "appearances", "both"], typing.Any] diff --git a/src/arcmira/mentions/types/count_mentions_request_src.py b/src/arcmira/mentions/types/count_mentions_request_src.py new file mode 100644 index 0000000..88ccd73 --- /dev/null +++ b/src/arcmira/mentions/types/count_mentions_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CountMentionsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/mentions/types/list_mentions_request_details.py b/src/arcmira/mentions/types/list_mentions_request_details.py new file mode 100644 index 0000000..1b4c9c1 --- /dev/null +++ b/src/arcmira/mentions/types/list_mentions_request_details.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestDetails = typing.Union[typing.Literal["full"], typing.Any] diff --git a/src/arcmira/mentions/types/list_mentions_request_entity_type.py b/src/arcmira/mentions/types/list_mentions_request_entity_type.py new file mode 100644 index 0000000..d171eb8 --- /dev/null +++ b/src/arcmira/mentions/types/list_mentions_request_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestEntityType = typing.Union[ + typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any +] diff --git a/src/arcmira/mentions/types/list_mentions_request_sentiment.py b/src/arcmira/mentions/types/list_mentions_request_sentiment.py new file mode 100644 index 0000000..65a4df7 --- /dev/null +++ b/src/arcmira/mentions/types/list_mentions_request_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/mentions/types/list_mentions_request_src.py b/src/arcmira/mentions/types/list_mentions_request_src.py new file mode 100644 index 0000000..84409bd --- /dev/null +++ b/src/arcmira/mentions/types/list_mentions_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMentionsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/meta/__init__.py b/src/arcmira/meta/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/meta/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/meta/client.py b/src/arcmira/meta/client.py new file mode 100644 index 0000000..a55ebb9 --- /dev/null +++ b/src/arcmira/meta/client.py @@ -0,0 +1,263 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.open_api_document import OpenApiDocument +from ..types.signup_sent_response import SignupSentResponse +from ..types.signup_verified_response import SignupVerifiedResponse +from .raw_client import AsyncRawMetaClient, RawMetaClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class MetaClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMetaClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMetaClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMetaClient + """ + return self._raw_client + + def get_openapi_document(self, *, request_options: typing.Optional[RequestOptions] = None) -> OpenApiDocument: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OpenApiDocument + This OpenAPI 3.1 document. + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.meta.get_openapi_document() + """ + _response = self._raw_client.get_openapi_document(request_options=request_options) + return _response.data + + def create_signup( + self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None + ) -> SignupSentResponse: + """ + Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. + + Parameters + ---------- + email : str + The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. + + src : typing.Optional[str] + The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SignupSentResponse + Code sent + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.meta.create_signup( + email="email", + ) + """ + _response = self._raw_client.create_signup(email=email, src=src, request_options=request_options) + return _response.data + + def verify_signup( + self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> SignupVerifiedResponse: + """ + Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. + + Parameters + ---------- + email : str + The address the code was sent to. + + code : str + The six digit code from the email. Ten minutes, five attempts, then a new send is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SignupVerifiedResponse + Account key minted + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.meta.verify_signup( + email="email", + code="code", + ) + """ + _response = self._raw_client.verify_signup(email=email, code=code, request_options=request_options) + return _response.data + + +class AsyncMetaClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMetaClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMetaClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMetaClient + """ + return self._raw_client + + async def get_openapi_document(self, *, request_options: typing.Optional[RequestOptions] = None) -> OpenApiDocument: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OpenApiDocument + This OpenAPI 3.1 document. + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.meta.get_openapi_document() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_openapi_document(request_options=request_options) + return _response.data + + async def create_signup( + self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None + ) -> SignupSentResponse: + """ + Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. + + Parameters + ---------- + email : str + The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. + + src : typing.Optional[str] + The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SignupSentResponse + Code sent + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.meta.create_signup( + email="email", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_signup(email=email, src=src, request_options=request_options) + return _response.data + + async def verify_signup( + self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> SignupVerifiedResponse: + """ + Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. + + Parameters + ---------- + email : str + The address the code was sent to. + + code : str + The six digit code from the email. Ten minutes, five attempts, then a new send is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SignupVerifiedResponse + Account key minted + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.meta.verify_signup( + email="email", + code="code", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.verify_signup(email=email, code=code, request_options=request_options) + return _response.data diff --git a/src/arcmira/meta/raw_client.py b/src/arcmira/meta/raw_client.py new file mode 100644 index 0000000..06d6abb --- /dev/null +++ b/src/arcmira/meta/raw_client.py @@ -0,0 +1,612 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.service_unavailable_error import ServiceUnavailableError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..types.error import Error +from ..types.open_api_document import OpenApiDocument +from ..types.signup_sent_response import SignupSentResponse +from ..types.signup_verified_response import SignupVerifiedResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawMetaClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_openapi_document( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[OpenApiDocument]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[OpenApiDocument] + This OpenAPI 3.1 document. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/openapi.json", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OpenApiDocument, + parse_obj_as( + type_=OpenApiDocument, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 create_signup( + self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SignupSentResponse]: + """ + Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. + + Parameters + ---------- + email : str + The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. + + src : typing.Optional[str] + The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SignupSentResponse] + Code sent + """ + _response = self._client_wrapper.httpx_client.request( + "v1/signups", + method="POST", + json={ + "email": email, + "src": src, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SignupSentResponse, + parse_obj_as( + type_=SignupSentResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 verify_signup( + self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SignupVerifiedResponse]: + """ + Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. + + Parameters + ---------- + email : str + The address the code was sent to. + + code : str + The six digit code from the email. Ten minutes, five attempts, then a new send is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SignupVerifiedResponse] + Account key minted + """ + _response = self._client_wrapper.httpx_client.request( + "v1/signups/verify", + method="POST", + json={ + "email": email, + "code": code, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SignupVerifiedResponse, + parse_obj_as( + type_=SignupVerifiedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawMetaClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_openapi_document( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[OpenApiDocument]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[OpenApiDocument] + This OpenAPI 3.1 document. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/openapi.json", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OpenApiDocument, + parse_obj_as( + type_=OpenApiDocument, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 create_signup( + self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SignupSentResponse]: + """ + Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. + + Parameters + ---------- + email : str + The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. + + src : typing.Optional[str] + The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SignupSentResponse] + Code sent + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/signups", + method="POST", + json={ + "email": email, + "src": src, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SignupSentResponse, + parse_obj_as( + type_=SignupSentResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 verify_signup( + self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SignupVerifiedResponse]: + """ + Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. + + Parameters + ---------- + email : str + The address the code was sent to. + + code : str + The six digit code from the email. Ten minutes, five attempts, then a new send is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SignupVerifiedResponse] + Account key minted + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/signups/verify", + method="POST", + json={ + "email": email, + "code": code, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SignupVerifiedResponse, + parse_obj_as( + type_=SignupVerifiedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/monitors/__init__.py b/src/arcmira/monitors/__init__.py new file mode 100644 index 0000000..061b697 --- /dev/null +++ b/src/arcmira/monitors/__init__.py @@ -0,0 +1,40 @@ +# 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 CreateMonitorsRequestNotifyFrequency, UpdateMonitorsRequestNotifyFrequency + from . import alerts, trackers +_dynamic_imports: typing.Dict[str, str] = { + "CreateMonitorsRequestNotifyFrequency": ".types", + "UpdateMonitorsRequestNotifyFrequency": ".types", + "alerts": ".alerts", + "trackers": ".trackers", +} + + +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__ = ["CreateMonitorsRequestNotifyFrequency", "UpdateMonitorsRequestNotifyFrequency", "alerts", "trackers"] diff --git a/src/arcmira/monitors/alerts/__init__.py b/src/arcmira/monitors/alerts/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/monitors/alerts/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/monitors/alerts/client.py b/src/arcmira/monitors/alerts/client.py new file mode 100644 index 0000000..688427c --- /dev/null +++ b/src/arcmira/monitors/alerts/client.py @@ -0,0 +1,118 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.alert_list_response import AlertListResponse +from .raw_client import AsyncRawAlertsClient, RawAlertsClient + + +class AlertsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawAlertsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawAlertsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawAlertsClient + """ + return self._raw_client + + def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AlertListResponse: + """ + The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Monitor id. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AlertListResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.alerts.list( + id="id", + ) + """ + _response = self._raw_client.list(id, n=n, request_options=request_options) + return _response.data + + +class AsyncAlertsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawAlertsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawAlertsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawAlertsClient + """ + return self._raw_client + + async def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AlertListResponse: + """ + The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Monitor id. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AlertListResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.alerts.list( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(id, n=n, request_options=request_options) + return _response.data diff --git a/src/arcmira/monitors/alerts/raw_client.py b/src/arcmira/monitors/alerts/raw_client.py new file mode 100644 index 0000000..1493b64 --- /dev/null +++ b/src/arcmira/monitors/alerts/raw_client.py @@ -0,0 +1,259 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.alert_list_response import AlertListResponse +from ...types.error import Error +from pydantic import ValidationError + + +class RawAlertsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[AlertListResponse]: + """ + The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Monitor id. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AlertListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/alerts", + method="GET", + params={ + "n": n, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AlertListResponse, + parse_obj_as( + type_=AlertListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawAlertsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[AlertListResponse]: + """ + The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Monitor id. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AlertListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/alerts", + method="GET", + params={ + "n": n, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AlertListResponse, + parse_obj_as( + type_=AlertListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/monitors/client.py b/src/arcmira/monitors/client.py new file mode 100644 index 0000000..7854299 --- /dev/null +++ b/src/arcmira/monitors/client.py @@ -0,0 +1,743 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.monitor_delete_response import MonitorDeleteResponse +from ..types.monitor_list_response import MonitorListResponse +from ..types.monitor_mutation_response import MonitorMutationResponse +from ..types.webhook_secret_rotate_response import WebhookSecretRotateResponse +from .raw_client import AsyncRawMonitorsClient, RawMonitorsClient +from .types.create_monitors_request_notify_frequency import CreateMonitorsRequestNotifyFrequency +from .types.update_monitors_request_notify_frequency import UpdateMonitorsRequestNotifyFrequency + +if typing.TYPE_CHECKING: + from .alerts.client import AlertsClient, AsyncAlertsClient + from .trackers.client import AsyncTrackersClient, TrackersClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class MonitorsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMonitorsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._trackers: typing.Optional[TrackersClient] = None + self._alerts: typing.Optional[AlertsClient] = None + + @property + def with_raw_response(self) -> RawMonitorsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMonitorsClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> MonitorListResponse: + """ + All monitors for the account with tracker counts, alert counts for the current calendar month, and Slack display metadata. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorListResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, + *, + name: str, + idempotency_key: typing.Optional[str] = None, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[CreateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorMutationResponse: + """ + Creating with notifyWebhook: true and a webhookUrl enables HMAC-signed webhook delivery and returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. All subsequent reads expose only webhookSecretSet and webhookSecretHint. + + Parameters + ---------- + name : str + Display name (1-100 characters). Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[CreateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorMutationResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.create( + name="name", + ) + """ + _response = self._raw_client.create( + name=name, + idempotency_key=idempotency_key, + notify_emails=notify_emails, + notify_frequency=notify_frequency, + digest_day=digest_day, + digest_time=digest_time, + notify_webhook=notify_webhook, + webhook_url=webhook_url, + notify_slack=notify_slack, + slack_integration_id=slack_integration_id, + slack_channel_id=slack_channel_id, + request_options=request_options, + ) + return _response.data + + def delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorDeleteResponse: + """ + Deletes the monitor AND every tracker inside it (trackersDeleted reports how many). Cannot be undone. Retrying with the original Idempotency-Key returns the original deleted count without deleting again. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorDeleteResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, idempotency_key=idempotency_key, request_options=request_options) + return _response.data + + def update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + name: typing.Optional[str] = OMIT, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[UpdateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + is_paused: typing.Optional[bool] = OMIT, + is_collapsed: typing.Optional[bool] = OMIT, + sort_order: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorMutationResponse: + """ + A PATCH that newly enables webhook signing (turns notifyWebhook on, or sets a webhookUrl where no secret existed before) returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Unrelated PATCHes expose only webhookSecretSet and webhookSecretHint. PATCHing notifyWebhook: true also re-enables a webhook that was auto-disabled after repeated failures and resets its failure counter. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + name : typing.Optional[str] + Display name (1-100 characters). Required on create. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[UpdateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + is_paused : typing.Optional[bool] + Paused monitors accept config changes but do not deliver; alerts that would have fired are not queued. + + is_collapsed : typing.Optional[bool] + Dashboard display state. + + sort_order : typing.Optional[int] + Dashboard sort position. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorMutationResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.update( + id="id", + ) + """ + _response = self._raw_client.update( + id, + idempotency_key=idempotency_key, + name=name, + notify_emails=notify_emails, + notify_frequency=notify_frequency, + digest_day=digest_day, + digest_time=digest_time, + notify_webhook=notify_webhook, + webhook_url=webhook_url, + notify_slack=notify_slack, + slack_integration_id=slack_integration_id, + slack_channel_id=slack_channel_id, + is_paused=is_paused, + is_collapsed=is_collapsed, + sort_order=sort_order, + request_options=request_options, + ) + return _response.data + + def rotate_webhook_secret( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> WebhookSecretRotateResponse: + """ + Generates a new signing secret and returns it in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Zero-downtime overlap: the previous secret remains valid until previousSecretExpiresAt (24 hours); during the window every delivery carries an additional X-Arcmira-Signature-Previous header computed with the old secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. After the window the old secret is dropped and the extra header disappears. Rotating again during the window replaces the previous secret and resets the window. Requires a configured webhook (webhookUrl set); otherwise 409 with code webhook_not_configured. Auto-disable interplay: rotation resets webhook_failures but never re-enables a webhook that was auto-disabled after repeated failures; to resume delivery, also PATCH the monitor with notifyWebhook: true. Requires the monitors:write scope. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebhookSecretRotateResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.rotate_webhook_secret( + id="id", + ) + """ + _response = self._raw_client.rotate_webhook_secret( + id, idempotency_key=idempotency_key, request_options=request_options + ) + return _response.data + + @property + def trackers(self): + if self._trackers is None: + from .trackers.client import TrackersClient # noqa: E402 + + self._trackers = TrackersClient(client_wrapper=self._client_wrapper) + return self._trackers + + @property + def alerts(self): + if self._alerts is None: + from .alerts.client import AlertsClient # noqa: E402 + + self._alerts = AlertsClient(client_wrapper=self._client_wrapper) + return self._alerts + + +class AsyncMonitorsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMonitorsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._trackers: typing.Optional[AsyncTrackersClient] = None + self._alerts: typing.Optional[AsyncAlertsClient] = None + + @property + def with_raw_response(self) -> AsyncRawMonitorsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMonitorsClient + """ + return self._raw_client + + async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> MonitorListResponse: + """ + All monitors for the account with tracker counts, alert counts for the current calendar month, and Slack display metadata. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorListResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, + *, + name: str, + idempotency_key: typing.Optional[str] = None, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[CreateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorMutationResponse: + """ + Creating with notifyWebhook: true and a webhookUrl enables HMAC-signed webhook delivery and returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. All subsequent reads expose only webhookSecretSet and webhookSecretHint. + + Parameters + ---------- + name : str + Display name (1-100 characters). Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[CreateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorMutationResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.create( + name="name", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + idempotency_key=idempotency_key, + notify_emails=notify_emails, + notify_frequency=notify_frequency, + digest_day=digest_day, + digest_time=digest_time, + notify_webhook=notify_webhook, + webhook_url=webhook_url, + notify_slack=notify_slack, + slack_integration_id=slack_integration_id, + slack_channel_id=slack_channel_id, + request_options=request_options, + ) + return _response.data + + async def delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorDeleteResponse: + """ + Deletes the monitor AND every tracker inside it (trackersDeleted reports how many). Cannot be undone. Retrying with the original Idempotency-Key returns the original deleted count without deleting again. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorDeleteResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, idempotency_key=idempotency_key, request_options=request_options) + return _response.data + + async def update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + name: typing.Optional[str] = OMIT, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[UpdateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + is_paused: typing.Optional[bool] = OMIT, + is_collapsed: typing.Optional[bool] = OMIT, + sort_order: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorMutationResponse: + """ + A PATCH that newly enables webhook signing (turns notifyWebhook on, or sets a webhookUrl where no secret existed before) returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Unrelated PATCHes expose only webhookSecretSet and webhookSecretHint. PATCHing notifyWebhook: true also re-enables a webhook that was auto-disabled after repeated failures and resets its failure counter. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + name : typing.Optional[str] + Display name (1-100 characters). Required on create. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[UpdateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + is_paused : typing.Optional[bool] + Paused monitors accept config changes but do not deliver; alerts that would have fired are not queued. + + is_collapsed : typing.Optional[bool] + Dashboard display state. + + sort_order : typing.Optional[int] + Dashboard sort position. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorMutationResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.update( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + id, + idempotency_key=idempotency_key, + name=name, + notify_emails=notify_emails, + notify_frequency=notify_frequency, + digest_day=digest_day, + digest_time=digest_time, + notify_webhook=notify_webhook, + webhook_url=webhook_url, + notify_slack=notify_slack, + slack_integration_id=slack_integration_id, + slack_channel_id=slack_channel_id, + is_paused=is_paused, + is_collapsed=is_collapsed, + sort_order=sort_order, + request_options=request_options, + ) + return _response.data + + async def rotate_webhook_secret( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> WebhookSecretRotateResponse: + """ + Generates a new signing secret and returns it in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Zero-downtime overlap: the previous secret remains valid until previousSecretExpiresAt (24 hours); during the window every delivery carries an additional X-Arcmira-Signature-Previous header computed with the old secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. After the window the old secret is dropped and the extra header disappears. Rotating again during the window replaces the previous secret and resets the window. Requires a configured webhook (webhookUrl set); otherwise 409 with code webhook_not_configured. Auto-disable interplay: rotation resets webhook_failures but never re-enables a webhook that was auto-disabled after repeated failures; to resume delivery, also PATCH the monitor with notifyWebhook: true. Requires the monitors:write scope. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebhookSecretRotateResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.rotate_webhook_secret( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.rotate_webhook_secret( + id, idempotency_key=idempotency_key, request_options=request_options + ) + return _response.data + + @property + def trackers(self): + if self._trackers is None: + from .trackers.client import AsyncTrackersClient # noqa: E402 + + self._trackers = AsyncTrackersClient(client_wrapper=self._client_wrapper) + return self._trackers + + @property + def alerts(self): + if self._alerts is None: + from .alerts.client import AsyncAlertsClient # noqa: E402 + + self._alerts = AsyncAlertsClient(client_wrapper=self._client_wrapper) + return self._alerts diff --git a/src/arcmira/monitors/raw_client.py b/src/arcmira/monitors/raw_client.py new file mode 100644 index 0000000..1052b5f --- /dev/null +++ b/src/arcmira/monitors/raw_client.py @@ -0,0 +1,1528 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.conflict_error import ConflictError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.monitor_delete_response import MonitorDeleteResponse +from ..types.monitor_list_response import MonitorListResponse +from ..types.monitor_mutation_response import MonitorMutationResponse +from ..types.webhook_secret_rotate_response import WebhookSecretRotateResponse +from .types.create_monitors_request_notify_frequency import CreateMonitorsRequestNotifyFrequency +from .types.update_monitors_request_notify_frequency import UpdateMonitorsRequestNotifyFrequency +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawMonitorsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[MonitorListResponse]: + """ + All monitors for the account with tracker counts, alert counts for the current calendar month, and Slack display metadata. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MonitorListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/monitors", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MonitorListResponse, + parse_obj_as( + type_=MonitorListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 create( + self, + *, + name: str, + idempotency_key: typing.Optional[str] = None, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[CreateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MonitorMutationResponse]: + """ + Creating with notifyWebhook: true and a webhookUrl enables HMAC-signed webhook delivery and returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. All subsequent reads expose only webhookSecretSet and webhookSecretHint. + + Parameters + ---------- + name : str + Display name (1-100 characters). Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[CreateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MonitorMutationResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/monitors", + method="POST", + json={ + "name": name, + "notifyEmails": notify_emails, + "notifyFrequency": notify_frequency, + "digestDay": digest_day, + "digestTime": digest_time, + "notifyWebhook": notify_webhook, + "webhookUrl": webhook_url, + "notifySlack": notify_slack, + "slackIntegrationId": slack_integration_id, + "slackChannelId": slack_channel_id, + }, + 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( + MonitorMutationResponse, + parse_obj_as( + type_=MonitorMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MonitorDeleteResponse]: + """ + Deletes the monitor AND every tracker inside it (trackersDeleted reports how many). Cannot be undone. Retrying with the original Idempotency-Key returns the original deleted count without deleting again. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MonitorDeleteResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}", + method="DELETE", + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MonitorDeleteResponse, + parse_obj_as( + type_=MonitorDeleteResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + name: typing.Optional[str] = OMIT, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[UpdateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + is_paused: typing.Optional[bool] = OMIT, + is_collapsed: typing.Optional[bool] = OMIT, + sort_order: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MonitorMutationResponse]: + """ + A PATCH that newly enables webhook signing (turns notifyWebhook on, or sets a webhookUrl where no secret existed before) returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Unrelated PATCHes expose only webhookSecretSet and webhookSecretHint. PATCHing notifyWebhook: true also re-enables a webhook that was auto-disabled after repeated failures and resets its failure counter. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + name : typing.Optional[str] + Display name (1-100 characters). Required on create. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[UpdateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + is_paused : typing.Optional[bool] + Paused monitors accept config changes but do not deliver; alerts that would have fired are not queued. + + is_collapsed : typing.Optional[bool] + Dashboard display state. + + sort_order : typing.Optional[int] + Dashboard sort position. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MonitorMutationResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}", + method="PATCH", + json={ + "name": name, + "notifyEmails": notify_emails, + "notifyFrequency": notify_frequency, + "digestDay": digest_day, + "digestTime": digest_time, + "notifyWebhook": notify_webhook, + "webhookUrl": webhook_url, + "notifySlack": notify_slack, + "slackIntegrationId": slack_integration_id, + "slackChannelId": slack_channel_id, + "isPaused": is_paused, + "isCollapsed": is_collapsed, + "sortOrder": sort_order, + }, + 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( + MonitorMutationResponse, + parse_obj_as( + type_=MonitorMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 rotate_webhook_secret( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[WebhookSecretRotateResponse]: + """ + Generates a new signing secret and returns it in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Zero-downtime overlap: the previous secret remains valid until previousSecretExpiresAt (24 hours); during the window every delivery carries an additional X-Arcmira-Signature-Previous header computed with the old secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. After the window the old secret is dropped and the extra header disappears. Rotating again during the window replaces the previous secret and resets the window. Requires a configured webhook (webhookUrl set); otherwise 409 with code webhook_not_configured. Auto-disable interplay: rotation resets webhook_failures but never re-enables a webhook that was auto-disabled after repeated failures; to resume delivery, also PATCH the monitor with notifyWebhook: true. Requires the monitors:write scope. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WebhookSecretRotateResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/webhook-secret/rotate", + method="POST", + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebhookSecretRotateResponse, + parse_obj_as( + type_=WebhookSecretRotateResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawMonitorsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MonitorListResponse]: + """ + All monitors for the account with tracker counts, alert counts for the current calendar month, and Slack display metadata. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MonitorListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/monitors", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MonitorListResponse, + parse_obj_as( + type_=MonitorListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 create( + self, + *, + name: str, + idempotency_key: typing.Optional[str] = None, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[CreateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MonitorMutationResponse]: + """ + Creating with notifyWebhook: true and a webhookUrl enables HMAC-signed webhook delivery and returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. All subsequent reads expose only webhookSecretSet and webhookSecretHint. + + Parameters + ---------- + name : str + Display name (1-100 characters). Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[CreateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MonitorMutationResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/monitors", + method="POST", + json={ + "name": name, + "notifyEmails": notify_emails, + "notifyFrequency": notify_frequency, + "digestDay": digest_day, + "digestTime": digest_time, + "notifyWebhook": notify_webhook, + "webhookUrl": webhook_url, + "notifySlack": notify_slack, + "slackIntegrationId": slack_integration_id, + "slackChannelId": slack_channel_id, + }, + 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( + MonitorMutationResponse, + parse_obj_as( + type_=MonitorMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MonitorDeleteResponse]: + """ + Deletes the monitor AND every tracker inside it (trackersDeleted reports how many). Cannot be undone. Retrying with the original Idempotency-Key returns the original deleted count without deleting again. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MonitorDeleteResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}", + method="DELETE", + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MonitorDeleteResponse, + parse_obj_as( + type_=MonitorDeleteResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + name: typing.Optional[str] = OMIT, + notify_emails: typing.Optional[typing.Sequence[str]] = OMIT, + notify_frequency: typing.Optional[UpdateMonitorsRequestNotifyFrequency] = OMIT, + digest_day: typing.Optional[str] = OMIT, + digest_time: typing.Optional[str] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + is_paused: typing.Optional[bool] = OMIT, + is_collapsed: typing.Optional[bool] = OMIT, + sort_order: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MonitorMutationResponse]: + """ + A PATCH that newly enables webhook signing (turns notifyWebhook on, or sets a webhookUrl where no secret existed before) returns the signing secret (monitor.webhookSecret) in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Unrelated PATCHes expose only webhookSecretSet and webhookSecretHint. PATCHing notifyWebhook: true also re-enables a webhook that was auto-disabled after repeated failures and resets its failure counter. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + name : typing.Optional[str] + Display name (1-100 characters). Required on create. + + notify_emails : typing.Optional[typing.Sequence[str]] + Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. + + notify_frequency : typing.Optional[UpdateMonitorsRequestNotifyFrequency] + Delivery cadence. Default realtime. Values: realtime (as analysis completes), hourly (hourly digest), daily (daily digest). Free-tier email delivery is coerced to daily regardless of the value sent. + + digest_day : typing.Optional[str] + Digest day of week. Default "monday". Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies. + + digest_time : typing.Optional[str] + Digest send hour as HH:MM (account timezone). Default "09:00". Applies to daily digests. + + notify_webhook : typing.Optional[bool] + Enable HMAC-signed webhook delivery (paid plans). When enabled together with webhookUrl, the response returns the signing secret (monitor.webhookSecret), recoverable with the original Idempotency-Key during the valid recovery window. PATCHing true also re-enables an auto-disabled webhook and resets its failure counter. + + webhook_url : typing.Optional[str] + Destination URL for webhook alert deliveries. + + notify_slack : typing.Optional[bool] + Enable Slack delivery. Requires a Slack integration connected in the dashboard. + + slack_integration_id : typing.Optional[str] + Slack integration id from the dashboard OAuth flow. + + slack_channel_id : typing.Optional[str] + Slack channel id to deliver to. + + is_paused : typing.Optional[bool] + Paused monitors accept config changes but do not deliver; alerts that would have fired are not queued. + + is_collapsed : typing.Optional[bool] + Dashboard display state. + + sort_order : typing.Optional[int] + Dashboard sort position. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MonitorMutationResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}", + method="PATCH", + json={ + "name": name, + "notifyEmails": notify_emails, + "notifyFrequency": notify_frequency, + "digestDay": digest_day, + "digestTime": digest_time, + "notifyWebhook": notify_webhook, + "webhookUrl": webhook_url, + "notifySlack": notify_slack, + "slackIntegrationId": slack_integration_id, + "slackChannelId": slack_channel_id, + "isPaused": is_paused, + "isCollapsed": is_collapsed, + "sortOrder": sort_order, + }, + 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( + MonitorMutationResponse, + parse_obj_as( + type_=MonitorMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 rotate_webhook_secret( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[WebhookSecretRotateResponse]: + """ + Generates a new signing secret and returns it in this response. Store it securely. A retry with the original Idempotency-Key recovers the same secret for up to 24 hours while it remains the current secret or the valid previous secret. An expired or displaced secret returns 409 idempotency_result_expired without rotating again. Reads do not expose the secret. Zero-downtime overlap: the previous secret remains valid until previousSecretExpiresAt (24 hours); during the window every delivery carries an additional X-Arcmira-Signature-Previous header computed with the old secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. After the window the old secret is dropped and the extra header disappears. Rotating again during the window replaces the previous secret and resets the window. Requires a configured webhook (webhookUrl set); otherwise 409 with code webhook_not_configured. Auto-disable interplay: rotation resets webhook_failures but never re-enables a webhook that was auto-disabled after repeated failures; to resume delivery, also PATCH the monitor with notifyWebhook: true. Requires the monitors:write scope. + + Parameters + ---------- + id : str + Monitor id. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WebhookSecretRotateResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/webhook-secret/rotate", + method="POST", + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebhookSecretRotateResponse, + parse_obj_as( + type_=WebhookSecretRotateResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/monitors/trackers/__init__.py b/src/arcmira/monitors/trackers/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/monitors/trackers/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/monitors/trackers/client.py b/src/arcmira/monitors/trackers/client.py new file mode 100644 index 0000000..84e4288 --- /dev/null +++ b/src/arcmira/monitors/trackers/client.py @@ -0,0 +1,214 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.monitor_add_trackers_response import MonitorAddTrackersResponse +from ...types.monitor_trackers_response import MonitorTrackersResponse +from .raw_client import AsyncRawTrackersClient, RawTrackersClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class TrackersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTrackersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTrackersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTrackersClient + """ + return self._raw_client + + def list(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> MonitorTrackersResponse: + """ + Parameters + ---------- + id : str + Monitor id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorTrackersResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.trackers.list( + id="id", + ) + """ + _response = self._raw_client.list(id, request_options=request_options) + return _response.data + + def add( + self, + id: str, + *, + tracker_ids: typing.Sequence[str], + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorAddTrackersResponse: + """ + Attaches EXISTING trackers to the monitor by id ({ trackerIds: ["trk_..."] }). It does not create trackers: create them first via POST /v1/trackers, then attach. Attached trackers use the monitor's delivery settings. Supply 1 to 90 IDs. Duplicate IDs count once. Every ID must belong to the account; a missing or foreign ID returns tracker_not_found and none are attached. attachedCount reports the unique attached count. + + Parameters + ---------- + id : str + Monitor id. + + tracker_ids : typing.Sequence[str] + Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorAddTrackersResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.monitors.trackers.add( + id="id", + tracker_ids=["trackerIds"], + ) + """ + _response = self._raw_client.add( + id, tracker_ids=tracker_ids, idempotency_key=idempotency_key, request_options=request_options + ) + return _response.data + + +class AsyncTrackersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTrackersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTrackersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTrackersClient + """ + return self._raw_client + + async def list( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> MonitorTrackersResponse: + """ + Parameters + ---------- + id : str + Monitor id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorTrackersResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.trackers.list( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(id, request_options=request_options) + return _response.data + + async def add( + self, + id: str, + *, + tracker_ids: typing.Sequence[str], + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MonitorAddTrackersResponse: + """ + Attaches EXISTING trackers to the monitor by id ({ trackerIds: ["trk_..."] }). It does not create trackers: create them first via POST /v1/trackers, then attach. Attached trackers use the monitor's delivery settings. Supply 1 to 90 IDs. Duplicate IDs count once. Every ID must belong to the account; a missing or foreign ID returns tracker_not_found and none are attached. attachedCount reports the unique attached count. + + Parameters + ---------- + id : str + Monitor id. + + tracker_ids : typing.Sequence[str] + Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MonitorAddTrackersResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.trackers.add( + id="id", + tracker_ids=["trackerIds"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.add( + id, tracker_ids=tracker_ids, idempotency_key=idempotency_key, request_options=request_options + ) + return _response.data diff --git a/src/arcmira/monitors/trackers/raw_client.py b/src/arcmira/monitors/trackers/raw_client.py new file mode 100644 index 0000000..bb09d00 --- /dev/null +++ b/src/arcmira/monitors/trackers/raw_client.py @@ -0,0 +1,528 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.conflict_error import ConflictError +from ...errors.forbidden_error import ForbiddenError +from ...errors.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.monitor_add_trackers_response import MonitorAddTrackersResponse +from ...types.monitor_trackers_response import MonitorTrackersResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawTrackersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MonitorTrackersResponse]: + """ + Parameters + ---------- + id : str + Monitor id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MonitorTrackersResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/trackers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MonitorTrackersResponse, + parse_obj_as( + type_=MonitorTrackersResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 add( + self, + id: str, + *, + tracker_ids: typing.Sequence[str], + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MonitorAddTrackersResponse]: + """ + Attaches EXISTING trackers to the monitor by id ({ trackerIds: ["trk_..."] }). It does not create trackers: create them first via POST /v1/trackers, then attach. Attached trackers use the monitor's delivery settings. Supply 1 to 90 IDs. Duplicate IDs count once. Every ID must belong to the account; a missing or foreign ID returns tracker_not_found and none are attached. attachedCount reports the unique attached count. + + Parameters + ---------- + id : str + Monitor id. + + tracker_ids : typing.Sequence[str] + Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MonitorAddTrackersResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/trackers", + method="POST", + json={ + "trackerIds": tracker_ids, + }, + 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( + MonitorAddTrackersResponse, + parse_obj_as( + type_=MonitorAddTrackersResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawTrackersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MonitorTrackersResponse]: + """ + Parameters + ---------- + id : str + Monitor id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MonitorTrackersResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/trackers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MonitorTrackersResponse, + parse_obj_as( + type_=MonitorTrackersResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 add( + self, + id: str, + *, + tracker_ids: typing.Sequence[str], + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MonitorAddTrackersResponse]: + """ + Attaches EXISTING trackers to the monitor by id ({ trackerIds: ["trk_..."] }). It does not create trackers: create them first via POST /v1/trackers, then attach. Attached trackers use the monitor's delivery settings. Supply 1 to 90 IDs. Duplicate IDs count once. Every ID must belong to the account; a missing or foreign ID returns tracker_not_found and none are attached. attachedCount reports the unique attached count. + + Parameters + ---------- + id : str + Monitor id. + + tracker_ids : typing.Sequence[str] + Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MonitorAddTrackersResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/monitors/{encode_path_param(id)}/trackers", + method="POST", + json={ + "trackerIds": tracker_ids, + }, + 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( + MonitorAddTrackersResponse, + parse_obj_as( + type_=MonitorAddTrackersResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/monitors/types/__init__.py b/src/arcmira/monitors/types/__init__.py new file mode 100644 index 0000000..6ae0606 --- /dev/null +++ b/src/arcmira/monitors/types/__init__.py @@ -0,0 +1,38 @@ +# 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 .create_monitors_request_notify_frequency import CreateMonitorsRequestNotifyFrequency + from .update_monitors_request_notify_frequency import UpdateMonitorsRequestNotifyFrequency +_dynamic_imports: typing.Dict[str, str] = { + "CreateMonitorsRequestNotifyFrequency": ".create_monitors_request_notify_frequency", + "UpdateMonitorsRequestNotifyFrequency": ".update_monitors_request_notify_frequency", +} + + +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__ = ["CreateMonitorsRequestNotifyFrequency", "UpdateMonitorsRequestNotifyFrequency"] diff --git a/src/arcmira/monitors/types/create_monitors_request_notify_frequency.py b/src/arcmira/monitors/types/create_monitors_request_notify_frequency.py new file mode 100644 index 0000000..136293b --- /dev/null +++ b/src/arcmira/monitors/types/create_monitors_request_notify_frequency.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreateMonitorsRequestNotifyFrequency = typing.Union[typing.Literal["realtime", "hourly", "daily"], typing.Any] diff --git a/src/arcmira/monitors/types/update_monitors_request_notify_frequency.py b/src/arcmira/monitors/types/update_monitors_request_notify_frequency.py new file mode 100644 index 0000000..88d88a2 --- /dev/null +++ b/src/arcmira/monitors/types/update_monitors_request_notify_frequency.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +UpdateMonitorsRequestNotifyFrequency = typing.Union[typing.Literal["realtime", "hourly", "daily"], typing.Any] diff --git a/src/arcmira/organizations/__init__.py b/src/arcmira/organizations/__init__.py new file mode 100644 index 0000000..c556990 --- /dev/null +++ b/src/arcmira/organizations/__init__.py @@ -0,0 +1,85 @@ +# 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 . import related + from .related import ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".related", + "ChannelsRelatedRequestMode": ".related", + "ChannelsRelatedRequestOrder": ".related", + "OrganizationsRelatedRequestIsAppearance": ".related", + "OrganizationsRelatedRequestMode": ".related", + "OrganizationsRelatedRequestOrder": ".related", + "PeopleRelatedRequestIsAppearance": ".related", + "PeopleRelatedRequestMode": ".related", + "PeopleRelatedRequestOrder": ".related", + "ProductsRelatedRequestIsAppearance": ".related", + "ProductsRelatedRequestMode": ".related", + "ProductsRelatedRequestOrder": ".related", + "TopicsRelatedRequestIsAppearance": ".related", + "TopicsRelatedRequestMode": ".related", + "TopicsRelatedRequestOrder": ".related", + "related": ".related", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", + "related", +] diff --git a/src/arcmira/organizations/client.py b/src/arcmira/organizations/client.py new file mode 100644 index 0000000..c62de42 --- /dev/null +++ b/src/arcmira/organizations/client.py @@ -0,0 +1,133 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.organization_page_response import OrganizationPageResponse +from .raw_client import AsyncRawOrganizationsClient, RawOrganizationsClient + +if typing.TYPE_CHECKING: + from .related.client import AsyncRelatedClient, RelatedClient + + +class OrganizationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawOrganizationsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._related: typing.Optional[RelatedClient] = None + + @property + def with_raw_response(self) -> RawOrganizationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawOrganizationsClient + """ + return self._raw_client + + def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> OrganizationPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationPageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.organizations.get( + slug="slug", + ) + """ + _response = self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def related(self): + if self._related is None: + from .related.client import RelatedClient # noqa: E402 + + self._related = RelatedClient(client_wrapper=self._client_wrapper) + return self._related + + +class AsyncOrganizationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawOrganizationsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._related: typing.Optional[AsyncRelatedClient] = None + + @property + def with_raw_response(self) -> AsyncRawOrganizationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawOrganizationsClient + """ + return self._raw_client + + async def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationPageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.organizations.get( + slug="slug", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def related(self): + if self._related is None: + from .related.client import AsyncRelatedClient # noqa: E402 + + self._related = AsyncRelatedClient(client_wrapper=self._client_wrapper) + return self._related diff --git a/src/arcmira/organizations/raw_client.py b/src/arcmira/organizations/raw_client.py new file mode 100644 index 0000000..4686295 --- /dev/null +++ b/src/arcmira/organizations/raw_client.py @@ -0,0 +1,268 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.organization_page_response import OrganizationPageResponse +from pydantic import ValidationError + + +class RawOrganizationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[OrganizationPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[OrganizationPageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OrganizationPageResponse, + parse_obj_as( + type_=OrganizationPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawOrganizationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[OrganizationPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[OrganizationPageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OrganizationPageResponse, + parse_obj_as( + type_=OrganizationPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/organizations/related/__init__.py b/src/arcmira/organizations/related/__init__.py new file mode 100644 index 0000000..bed85d9 --- /dev/null +++ b/src/arcmira/organizations/related/__init__.py @@ -0,0 +1,82 @@ +# 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 ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".types", + "ChannelsRelatedRequestMode": ".types", + "ChannelsRelatedRequestOrder": ".types", + "OrganizationsRelatedRequestIsAppearance": ".types", + "OrganizationsRelatedRequestMode": ".types", + "OrganizationsRelatedRequestOrder": ".types", + "PeopleRelatedRequestIsAppearance": ".types", + "PeopleRelatedRequestMode": ".types", + "PeopleRelatedRequestOrder": ".types", + "ProductsRelatedRequestIsAppearance": ".types", + "ProductsRelatedRequestMode": ".types", + "ProductsRelatedRequestOrder": ".types", + "TopicsRelatedRequestIsAppearance": ".types", + "TopicsRelatedRequestMode": ".types", + "TopicsRelatedRequestOrder": ".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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/organizations/related/client.py b/src/arcmira/organizations/related/client.py new file mode 100644 index 0000000..12f1ce2 --- /dev/null +++ b/src/arcmira/organizations/related/client.py @@ -0,0 +1,930 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from .raw_client import AsyncRawRelatedClient, RawRelatedClient +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder + + +class RelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRelatedClient + """ + return self._raw_client + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.organizations.related.topics( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.organizations.related.people( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.organizations.related.organizations( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.organizations.related.products( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.organizations.related.channels( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRelatedClient + """ + return self._raw_client + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.organizations.related.topics( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.organizations.related.people( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.organizations.related.organizations( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.organizations.related.products( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.organizations.related.channels( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/organizations/related/raw_client.py b/src/arcmira/organizations/related/raw_client.py new file mode 100644 index 0000000..0e34220 --- /dev/null +++ b/src/arcmira/organizations/related/raw_client.py @@ -0,0 +1,1861 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from ...types.error import Error +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder +from pydantic import ValidationError + + +class RawRelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/organizations/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/organizations/related/types/__init__.py b/src/arcmira/organizations/related/types/__init__.py new file mode 100644 index 0000000..1f3a8a7 --- /dev/null +++ b/src/arcmira/organizations/related/types/__init__.py @@ -0,0 +1,80 @@ +# 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 .channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance + from .channels_related_request_mode import ChannelsRelatedRequestMode + from .channels_related_request_order import ChannelsRelatedRequestOrder + from .organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance + from .organizations_related_request_mode import OrganizationsRelatedRequestMode + from .organizations_related_request_order import OrganizationsRelatedRequestOrder + from .people_related_request_is_appearance import PeopleRelatedRequestIsAppearance + from .people_related_request_mode import PeopleRelatedRequestMode + from .people_related_request_order import PeopleRelatedRequestOrder + from .products_related_request_is_appearance import ProductsRelatedRequestIsAppearance + from .products_related_request_mode import ProductsRelatedRequestMode + from .products_related_request_order import ProductsRelatedRequestOrder + from .topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance + from .topics_related_request_mode import TopicsRelatedRequestMode + from .topics_related_request_order import TopicsRelatedRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".channels_related_request_is_appearance", + "ChannelsRelatedRequestMode": ".channels_related_request_mode", + "ChannelsRelatedRequestOrder": ".channels_related_request_order", + "OrganizationsRelatedRequestIsAppearance": ".organizations_related_request_is_appearance", + "OrganizationsRelatedRequestMode": ".organizations_related_request_mode", + "OrganizationsRelatedRequestOrder": ".organizations_related_request_order", + "PeopleRelatedRequestIsAppearance": ".people_related_request_is_appearance", + "PeopleRelatedRequestMode": ".people_related_request_mode", + "PeopleRelatedRequestOrder": ".people_related_request_order", + "ProductsRelatedRequestIsAppearance": ".products_related_request_is_appearance", + "ProductsRelatedRequestMode": ".products_related_request_mode", + "ProductsRelatedRequestOrder": ".products_related_request_order", + "TopicsRelatedRequestIsAppearance": ".topics_related_request_is_appearance", + "TopicsRelatedRequestMode": ".topics_related_request_mode", + "TopicsRelatedRequestOrder": ".topics_related_request_order", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/organizations/related/types/channels_related_request_is_appearance.py b/src/arcmira/organizations/related/types/channels_related_request_is_appearance.py new file mode 100644 index 0000000..e21cd2c --- /dev/null +++ b/src/arcmira/organizations/related/types/channels_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/organizations/related/types/channels_related_request_mode.py b/src/arcmira/organizations/related/types/channels_related_request_mode.py new file mode 100644 index 0000000..49a4137 --- /dev/null +++ b/src/arcmira/organizations/related/types/channels_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/organizations/related/types/channels_related_request_order.py b/src/arcmira/organizations/related/types/channels_related_request_order.py new file mode 100644 index 0000000..0f5b302 --- /dev/null +++ b/src/arcmira/organizations/related/types/channels_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/organizations/related/types/organizations_related_request_is_appearance.py b/src/arcmira/organizations/related/types/organizations_related_request_is_appearance.py new file mode 100644 index 0000000..2762d60 --- /dev/null +++ b/src/arcmira/organizations/related/types/organizations_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/organizations/related/types/organizations_related_request_mode.py b/src/arcmira/organizations/related/types/organizations_related_request_mode.py new file mode 100644 index 0000000..2bdd64b --- /dev/null +++ b/src/arcmira/organizations/related/types/organizations_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/organizations/related/types/organizations_related_request_order.py b/src/arcmira/organizations/related/types/organizations_related_request_order.py new file mode 100644 index 0000000..4c5dde2 --- /dev/null +++ b/src/arcmira/organizations/related/types/organizations_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/organizations/related/types/people_related_request_is_appearance.py b/src/arcmira/organizations/related/types/people_related_request_is_appearance.py new file mode 100644 index 0000000..af591fc --- /dev/null +++ b/src/arcmira/organizations/related/types/people_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/organizations/related/types/people_related_request_mode.py b/src/arcmira/organizations/related/types/people_related_request_mode.py new file mode 100644 index 0000000..9d9b51c --- /dev/null +++ b/src/arcmira/organizations/related/types/people_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/organizations/related/types/people_related_request_order.py b/src/arcmira/organizations/related/types/people_related_request_order.py new file mode 100644 index 0000000..a0d19ad --- /dev/null +++ b/src/arcmira/organizations/related/types/people_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/organizations/related/types/products_related_request_is_appearance.py b/src/arcmira/organizations/related/types/products_related_request_is_appearance.py new file mode 100644 index 0000000..a6aba02 --- /dev/null +++ b/src/arcmira/organizations/related/types/products_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/organizations/related/types/products_related_request_mode.py b/src/arcmira/organizations/related/types/products_related_request_mode.py new file mode 100644 index 0000000..9046641 --- /dev/null +++ b/src/arcmira/organizations/related/types/products_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/organizations/related/types/products_related_request_order.py b/src/arcmira/organizations/related/types/products_related_request_order.py new file mode 100644 index 0000000..3e5eb9f --- /dev/null +++ b/src/arcmira/organizations/related/types/products_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/organizations/related/types/topics_related_request_is_appearance.py b/src/arcmira/organizations/related/types/topics_related_request_is_appearance.py new file mode 100644 index 0000000..2b7c65e --- /dev/null +++ b/src/arcmira/organizations/related/types/topics_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/organizations/related/types/topics_related_request_mode.py b/src/arcmira/organizations/related/types/topics_related_request_mode.py new file mode 100644 index 0000000..090ee69 --- /dev/null +++ b/src/arcmira/organizations/related/types/topics_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/organizations/related/types/topics_related_request_order.py b/src/arcmira/organizations/related/types/topics_related_request_order.py new file mode 100644 index 0000000..56c645a --- /dev/null +++ b/src/arcmira/organizations/related/types/topics_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/people/__init__.py b/src/arcmira/people/__init__.py new file mode 100644 index 0000000..f020fad --- /dev/null +++ b/src/arcmira/people/__init__.py @@ -0,0 +1,94 @@ +# 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 . import appearances, related + from .appearances import ListAppearancesRequestIsAppearance, ListAppearancesRequestMode, ListAppearancesRequestOrder + from .related import ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".related", + "ChannelsRelatedRequestMode": ".related", + "ChannelsRelatedRequestOrder": ".related", + "ListAppearancesRequestIsAppearance": ".appearances", + "ListAppearancesRequestMode": ".appearances", + "ListAppearancesRequestOrder": ".appearances", + "OrganizationsRelatedRequestIsAppearance": ".related", + "OrganizationsRelatedRequestMode": ".related", + "OrganizationsRelatedRequestOrder": ".related", + "PeopleRelatedRequestIsAppearance": ".related", + "PeopleRelatedRequestMode": ".related", + "PeopleRelatedRequestOrder": ".related", + "ProductsRelatedRequestIsAppearance": ".related", + "ProductsRelatedRequestMode": ".related", + "ProductsRelatedRequestOrder": ".related", + "TopicsRelatedRequestIsAppearance": ".related", + "TopicsRelatedRequestMode": ".related", + "TopicsRelatedRequestOrder": ".related", + "appearances": ".appearances", + "related": ".related", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "ListAppearancesRequestIsAppearance", + "ListAppearancesRequestMode", + "ListAppearancesRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", + "appearances", + "related", +] diff --git a/src/arcmira/people/appearances/__init__.py b/src/arcmira/people/appearances/__init__.py new file mode 100644 index 0000000..74a583c --- /dev/null +++ b/src/arcmira/people/appearances/__init__.py @@ -0,0 +1,38 @@ +# 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 ListAppearancesRequestIsAppearance, ListAppearancesRequestMode, ListAppearancesRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ListAppearancesRequestIsAppearance": ".types", + "ListAppearancesRequestMode": ".types", + "ListAppearancesRequestOrder": ".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__ = ["ListAppearancesRequestIsAppearance", "ListAppearancesRequestMode", "ListAppearancesRequestOrder"] diff --git a/src/arcmira/people/appearances/client.py b/src/arcmira/people/appearances/client.py new file mode 100644 index 0000000..a8f6861 --- /dev/null +++ b/src/arcmira/people/appearances/client.py @@ -0,0 +1,218 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.person_appearance_list_response import PersonAppearanceListResponse +from ...types.person_appearance_list_response_items_item import PersonAppearanceListResponseItemsItem +from .raw_client import AsyncRawAppearancesClient, RawAppearancesClient +from .types.list_appearances_request_is_appearance import ListAppearancesRequestIsAppearance +from .types.list_appearances_request_mode import ListAppearancesRequestMode +from .types.list_appearances_request_order import ListAppearancesRequestOrder + + +class AppearancesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawAppearancesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawAppearancesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawAppearancesClient + """ + return self._raw_client + + def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListAppearancesRequestOrder] = None, + mode: typing.Optional[ListAppearancesRequestMode] = None, + is_appearance: typing.Optional[ListAppearancesRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: + """ + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListAppearancesRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListAppearancesRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListAppearancesRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.people.appearances.list( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncAppearancesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawAppearancesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawAppearancesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawAppearancesClient + """ + return self._raw_client + + async def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListAppearancesRequestOrder] = None, + mode: typing.Optional[ListAppearancesRequestMode] = None, + is_appearance: typing.Optional[ListAppearancesRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: + """ + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListAppearancesRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListAppearancesRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListAppearancesRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.people.appearances.list( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/people/appearances/raw_client.py b/src/arcmira/people/appearances/raw_client.py new file mode 100644 index 0000000..dd2bedd --- /dev/null +++ b/src/arcmira/people/appearances/raw_client.py @@ -0,0 +1,397 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.person_appearance_list_response import PersonAppearanceListResponse +from ...types.person_appearance_list_response_items_item import PersonAppearanceListResponseItemsItem +from .types.list_appearances_request_is_appearance import ListAppearancesRequestIsAppearance +from .types.list_appearances_request_mode import ListAppearancesRequestMode +from .types.list_appearances_request_order import ListAppearancesRequestOrder +from pydantic import ValidationError + + +class RawAppearancesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListAppearancesRequestOrder] = None, + mode: typing.Optional[ListAppearancesRequestMode] = None, + is_appearance: typing.Optional[ListAppearancesRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: + """ + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListAppearancesRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListAppearancesRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListAppearancesRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/appearances", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + PersonAppearanceListResponse, + parse_obj_as( + type_=PersonAppearanceListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawAppearancesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ListAppearancesRequestOrder] = None, + mode: typing.Optional[ListAppearancesRequestMode] = None, + is_appearance: typing.Optional[ListAppearancesRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: + """ + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ListAppearancesRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ListAppearancesRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ListAppearancesRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/appearances", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + PersonAppearanceListResponse, + parse_obj_as( + type_=PersonAppearanceListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/people/appearances/types/__init__.py b/src/arcmira/people/appearances/types/__init__.py new file mode 100644 index 0000000..0b0790e --- /dev/null +++ b/src/arcmira/people/appearances/types/__init__.py @@ -0,0 +1,40 @@ +# 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 .list_appearances_request_is_appearance import ListAppearancesRequestIsAppearance + from .list_appearances_request_mode import ListAppearancesRequestMode + from .list_appearances_request_order import ListAppearancesRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ListAppearancesRequestIsAppearance": ".list_appearances_request_is_appearance", + "ListAppearancesRequestMode": ".list_appearances_request_mode", + "ListAppearancesRequestOrder": ".list_appearances_request_order", +} + + +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__ = ["ListAppearancesRequestIsAppearance", "ListAppearancesRequestMode", "ListAppearancesRequestOrder"] diff --git a/src/arcmira/people/appearances/types/list_appearances_request_is_appearance.py b/src/arcmira/people/appearances/types/list_appearances_request_is_appearance.py new file mode 100644 index 0000000..1333b2f --- /dev/null +++ b/src/arcmira/people/appearances/types/list_appearances_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListAppearancesRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/people/appearances/types/list_appearances_request_mode.py b/src/arcmira/people/appearances/types/list_appearances_request_mode.py new file mode 100644 index 0000000..98be017 --- /dev/null +++ b/src/arcmira/people/appearances/types/list_appearances_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListAppearancesRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/people/appearances/types/list_appearances_request_order.py b/src/arcmira/people/appearances/types/list_appearances_request_order.py new file mode 100644 index 0000000..ac5ac90 --- /dev/null +++ b/src/arcmira/people/appearances/types/list_appearances_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListAppearancesRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/people/client.py b/src/arcmira/people/client.py new file mode 100644 index 0000000..48e7816 --- /dev/null +++ b/src/arcmira/people/client.py @@ -0,0 +1,150 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.person_page_response import PersonPageResponse +from .raw_client import AsyncRawPeopleClient, RawPeopleClient + +if typing.TYPE_CHECKING: + from .appearances.client import AppearancesClient, AsyncAppearancesClient + from .related.client import AsyncRelatedClient, RelatedClient + + +class PeopleClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawPeopleClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._appearances: typing.Optional[AppearancesClient] = None + self._related: typing.Optional[RelatedClient] = None + + @property + def with_raw_response(self) -> RawPeopleClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawPeopleClient + """ + return self._raw_client + + def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> PersonPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PersonPageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.people.get( + slug="slug", + ) + """ + _response = self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def appearances(self): + if self._appearances is None: + from .appearances.client import AppearancesClient # noqa: E402 + + self._appearances = AppearancesClient(client_wrapper=self._client_wrapper) + return self._appearances + + @property + def related(self): + if self._related is None: + from .related.client import RelatedClient # noqa: E402 + + self._related = RelatedClient(client_wrapper=self._client_wrapper) + return self._related + + +class AsyncPeopleClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawPeopleClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._appearances: typing.Optional[AsyncAppearancesClient] = None + self._related: typing.Optional[AsyncRelatedClient] = None + + @property + def with_raw_response(self) -> AsyncRawPeopleClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawPeopleClient + """ + return self._raw_client + + async def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> PersonPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PersonPageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.people.get( + slug="slug", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def appearances(self): + if self._appearances is None: + from .appearances.client import AsyncAppearancesClient # noqa: E402 + + self._appearances = AsyncAppearancesClient(client_wrapper=self._client_wrapper) + return self._appearances + + @property + def related(self): + if self._related is None: + from .related.client import AsyncRelatedClient # noqa: E402 + + self._related = AsyncRelatedClient(client_wrapper=self._client_wrapper) + return self._related diff --git a/src/arcmira/people/raw_client.py b/src/arcmira/people/raw_client.py new file mode 100644 index 0000000..60f9b1f --- /dev/null +++ b/src/arcmira/people/raw_client.py @@ -0,0 +1,268 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.person_page_response import PersonPageResponse +from pydantic import ValidationError + + +class RawPeopleClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[PersonPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PersonPageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PersonPageResponse, + parse_obj_as( + type_=PersonPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawPeopleClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[PersonPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PersonPageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PersonPageResponse, + parse_obj_as( + type_=PersonPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/people/related/__init__.py b/src/arcmira/people/related/__init__.py new file mode 100644 index 0000000..bed85d9 --- /dev/null +++ b/src/arcmira/people/related/__init__.py @@ -0,0 +1,82 @@ +# 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 ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".types", + "ChannelsRelatedRequestMode": ".types", + "ChannelsRelatedRequestOrder": ".types", + "OrganizationsRelatedRequestIsAppearance": ".types", + "OrganizationsRelatedRequestMode": ".types", + "OrganizationsRelatedRequestOrder": ".types", + "PeopleRelatedRequestIsAppearance": ".types", + "PeopleRelatedRequestMode": ".types", + "PeopleRelatedRequestOrder": ".types", + "ProductsRelatedRequestIsAppearance": ".types", + "ProductsRelatedRequestMode": ".types", + "ProductsRelatedRequestOrder": ".types", + "TopicsRelatedRequestIsAppearance": ".types", + "TopicsRelatedRequestMode": ".types", + "TopicsRelatedRequestOrder": ".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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/people/related/client.py b/src/arcmira/people/related/client.py new file mode 100644 index 0000000..0a54cb2 --- /dev/null +++ b/src/arcmira/people/related/client.py @@ -0,0 +1,930 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from .raw_client import AsyncRawRelatedClient, RawRelatedClient +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder + + +class RelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRelatedClient + """ + return self._raw_client + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.people.related.topics( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.people.related.people( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.people.related.organizations( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.people.related.products( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.people.related.channels( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRelatedClient + """ + return self._raw_client + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.people.related.topics( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.people.related.people( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.people.related.organizations( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.people.related.products( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.people.related.channels( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/people/related/raw_client.py b/src/arcmira/people/related/raw_client.py new file mode 100644 index 0000000..8be7530 --- /dev/null +++ b/src/arcmira/people/related/raw_client.py @@ -0,0 +1,1861 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from ...types.error import Error +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder +from pydantic import ValidationError + + +class RawRelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/people/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/people/related/types/__init__.py b/src/arcmira/people/related/types/__init__.py new file mode 100644 index 0000000..1f3a8a7 --- /dev/null +++ b/src/arcmira/people/related/types/__init__.py @@ -0,0 +1,80 @@ +# 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 .channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance + from .channels_related_request_mode import ChannelsRelatedRequestMode + from .channels_related_request_order import ChannelsRelatedRequestOrder + from .organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance + from .organizations_related_request_mode import OrganizationsRelatedRequestMode + from .organizations_related_request_order import OrganizationsRelatedRequestOrder + from .people_related_request_is_appearance import PeopleRelatedRequestIsAppearance + from .people_related_request_mode import PeopleRelatedRequestMode + from .people_related_request_order import PeopleRelatedRequestOrder + from .products_related_request_is_appearance import ProductsRelatedRequestIsAppearance + from .products_related_request_mode import ProductsRelatedRequestMode + from .products_related_request_order import ProductsRelatedRequestOrder + from .topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance + from .topics_related_request_mode import TopicsRelatedRequestMode + from .topics_related_request_order import TopicsRelatedRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".channels_related_request_is_appearance", + "ChannelsRelatedRequestMode": ".channels_related_request_mode", + "ChannelsRelatedRequestOrder": ".channels_related_request_order", + "OrganizationsRelatedRequestIsAppearance": ".organizations_related_request_is_appearance", + "OrganizationsRelatedRequestMode": ".organizations_related_request_mode", + "OrganizationsRelatedRequestOrder": ".organizations_related_request_order", + "PeopleRelatedRequestIsAppearance": ".people_related_request_is_appearance", + "PeopleRelatedRequestMode": ".people_related_request_mode", + "PeopleRelatedRequestOrder": ".people_related_request_order", + "ProductsRelatedRequestIsAppearance": ".products_related_request_is_appearance", + "ProductsRelatedRequestMode": ".products_related_request_mode", + "ProductsRelatedRequestOrder": ".products_related_request_order", + "TopicsRelatedRequestIsAppearance": ".topics_related_request_is_appearance", + "TopicsRelatedRequestMode": ".topics_related_request_mode", + "TopicsRelatedRequestOrder": ".topics_related_request_order", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/people/related/types/channels_related_request_is_appearance.py b/src/arcmira/people/related/types/channels_related_request_is_appearance.py new file mode 100644 index 0000000..e21cd2c --- /dev/null +++ b/src/arcmira/people/related/types/channels_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/people/related/types/channels_related_request_mode.py b/src/arcmira/people/related/types/channels_related_request_mode.py new file mode 100644 index 0000000..49a4137 --- /dev/null +++ b/src/arcmira/people/related/types/channels_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/people/related/types/channels_related_request_order.py b/src/arcmira/people/related/types/channels_related_request_order.py new file mode 100644 index 0000000..0f5b302 --- /dev/null +++ b/src/arcmira/people/related/types/channels_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/people/related/types/organizations_related_request_is_appearance.py b/src/arcmira/people/related/types/organizations_related_request_is_appearance.py new file mode 100644 index 0000000..2762d60 --- /dev/null +++ b/src/arcmira/people/related/types/organizations_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/people/related/types/organizations_related_request_mode.py b/src/arcmira/people/related/types/organizations_related_request_mode.py new file mode 100644 index 0000000..2bdd64b --- /dev/null +++ b/src/arcmira/people/related/types/organizations_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/people/related/types/organizations_related_request_order.py b/src/arcmira/people/related/types/organizations_related_request_order.py new file mode 100644 index 0000000..4c5dde2 --- /dev/null +++ b/src/arcmira/people/related/types/organizations_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/people/related/types/people_related_request_is_appearance.py b/src/arcmira/people/related/types/people_related_request_is_appearance.py new file mode 100644 index 0000000..af591fc --- /dev/null +++ b/src/arcmira/people/related/types/people_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/people/related/types/people_related_request_mode.py b/src/arcmira/people/related/types/people_related_request_mode.py new file mode 100644 index 0000000..9d9b51c --- /dev/null +++ b/src/arcmira/people/related/types/people_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/people/related/types/people_related_request_order.py b/src/arcmira/people/related/types/people_related_request_order.py new file mode 100644 index 0000000..a0d19ad --- /dev/null +++ b/src/arcmira/people/related/types/people_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/people/related/types/products_related_request_is_appearance.py b/src/arcmira/people/related/types/products_related_request_is_appearance.py new file mode 100644 index 0000000..a6aba02 --- /dev/null +++ b/src/arcmira/people/related/types/products_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/people/related/types/products_related_request_mode.py b/src/arcmira/people/related/types/products_related_request_mode.py new file mode 100644 index 0000000..9046641 --- /dev/null +++ b/src/arcmira/people/related/types/products_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/people/related/types/products_related_request_order.py b/src/arcmira/people/related/types/products_related_request_order.py new file mode 100644 index 0000000..3e5eb9f --- /dev/null +++ b/src/arcmira/people/related/types/products_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/people/related/types/topics_related_request_is_appearance.py b/src/arcmira/people/related/types/topics_related_request_is_appearance.py new file mode 100644 index 0000000..2b7c65e --- /dev/null +++ b/src/arcmira/people/related/types/topics_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/people/related/types/topics_related_request_mode.py b/src/arcmira/people/related/types/topics_related_request_mode.py new file mode 100644 index 0000000..090ee69 --- /dev/null +++ b/src/arcmira/people/related/types/topics_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/people/related/types/topics_related_request_order.py b/src/arcmira/people/related/types/topics_related_request_order.py new file mode 100644 index 0000000..56c645a --- /dev/null +++ b/src/arcmira/people/related/types/topics_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/products/__init__.py b/src/arcmira/products/__init__.py new file mode 100644 index 0000000..c556990 --- /dev/null +++ b/src/arcmira/products/__init__.py @@ -0,0 +1,85 @@ +# 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 . import related + from .related import ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".related", + "ChannelsRelatedRequestMode": ".related", + "ChannelsRelatedRequestOrder": ".related", + "OrganizationsRelatedRequestIsAppearance": ".related", + "OrganizationsRelatedRequestMode": ".related", + "OrganizationsRelatedRequestOrder": ".related", + "PeopleRelatedRequestIsAppearance": ".related", + "PeopleRelatedRequestMode": ".related", + "PeopleRelatedRequestOrder": ".related", + "ProductsRelatedRequestIsAppearance": ".related", + "ProductsRelatedRequestMode": ".related", + "ProductsRelatedRequestOrder": ".related", + "TopicsRelatedRequestIsAppearance": ".related", + "TopicsRelatedRequestMode": ".related", + "TopicsRelatedRequestOrder": ".related", + "related": ".related", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", + "related", +] diff --git a/src/arcmira/products/client.py b/src/arcmira/products/client.py new file mode 100644 index 0000000..75ee258 --- /dev/null +++ b/src/arcmira/products/client.py @@ -0,0 +1,131 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.product_page_response import ProductPageResponse +from .raw_client import AsyncRawProductsClient, RawProductsClient + +if typing.TYPE_CHECKING: + from .related.client import AsyncRelatedClient, RelatedClient + + +class ProductsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawProductsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._related: typing.Optional[RelatedClient] = None + + @property + def with_raw_response(self) -> RawProductsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawProductsClient + """ + return self._raw_client + + def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> ProductPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProductPageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.products.get( + slug="slug", + ) + """ + _response = self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def related(self): + if self._related is None: + from .related.client import RelatedClient # noqa: E402 + + self._related = RelatedClient(client_wrapper=self._client_wrapper) + return self._related + + +class AsyncProductsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawProductsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._related: typing.Optional[AsyncRelatedClient] = None + + @property + def with_raw_response(self) -> AsyncRawProductsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawProductsClient + """ + return self._raw_client + + async def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> ProductPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProductPageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.products.get( + slug="slug", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def related(self): + if self._related is None: + from .related.client import AsyncRelatedClient # noqa: E402 + + self._related = AsyncRelatedClient(client_wrapper=self._client_wrapper) + return self._related diff --git a/src/arcmira/products/raw_client.py b/src/arcmira/products/raw_client.py new file mode 100644 index 0000000..4ce3f7c --- /dev/null +++ b/src/arcmira/products/raw_client.py @@ -0,0 +1,268 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.product_page_response import ProductPageResponse +from pydantic import ValidationError + + +class RawProductsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ProductPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ProductPageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProductPageResponse, + parse_obj_as( + type_=ProductPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawProductsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ProductPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ProductPageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProductPageResponse, + parse_obj_as( + type_=ProductPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/products/related/__init__.py b/src/arcmira/products/related/__init__.py new file mode 100644 index 0000000..bed85d9 --- /dev/null +++ b/src/arcmira/products/related/__init__.py @@ -0,0 +1,82 @@ +# 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 ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".types", + "ChannelsRelatedRequestMode": ".types", + "ChannelsRelatedRequestOrder": ".types", + "OrganizationsRelatedRequestIsAppearance": ".types", + "OrganizationsRelatedRequestMode": ".types", + "OrganizationsRelatedRequestOrder": ".types", + "PeopleRelatedRequestIsAppearance": ".types", + "PeopleRelatedRequestMode": ".types", + "PeopleRelatedRequestOrder": ".types", + "ProductsRelatedRequestIsAppearance": ".types", + "ProductsRelatedRequestMode": ".types", + "ProductsRelatedRequestOrder": ".types", + "TopicsRelatedRequestIsAppearance": ".types", + "TopicsRelatedRequestMode": ".types", + "TopicsRelatedRequestOrder": ".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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/products/related/client.py b/src/arcmira/products/related/client.py new file mode 100644 index 0000000..c249e54 --- /dev/null +++ b/src/arcmira/products/related/client.py @@ -0,0 +1,930 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from .raw_client import AsyncRawRelatedClient, RawRelatedClient +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder + + +class RelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRelatedClient + """ + return self._raw_client + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.products.related.topics( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.products.related.people( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.products.related.organizations( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.products.related.products( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.products.related.channels( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRelatedClient + """ + return self._raw_client + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.products.related.topics( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.products.related.people( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.products.related.organizations( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.products.related.products( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.products.related.channels( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/products/related/raw_client.py b/src/arcmira/products/related/raw_client.py new file mode 100644 index 0000000..7a8d1fb --- /dev/null +++ b/src/arcmira/products/related/raw_client.py @@ -0,0 +1,1861 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from ...types.error import Error +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder +from pydantic import ValidationError + + +class RawRelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/products/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/products/related/types/__init__.py b/src/arcmira/products/related/types/__init__.py new file mode 100644 index 0000000..1f3a8a7 --- /dev/null +++ b/src/arcmira/products/related/types/__init__.py @@ -0,0 +1,80 @@ +# 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 .channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance + from .channels_related_request_mode import ChannelsRelatedRequestMode + from .channels_related_request_order import ChannelsRelatedRequestOrder + from .organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance + from .organizations_related_request_mode import OrganizationsRelatedRequestMode + from .organizations_related_request_order import OrganizationsRelatedRequestOrder + from .people_related_request_is_appearance import PeopleRelatedRequestIsAppearance + from .people_related_request_mode import PeopleRelatedRequestMode + from .people_related_request_order import PeopleRelatedRequestOrder + from .products_related_request_is_appearance import ProductsRelatedRequestIsAppearance + from .products_related_request_mode import ProductsRelatedRequestMode + from .products_related_request_order import ProductsRelatedRequestOrder + from .topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance + from .topics_related_request_mode import TopicsRelatedRequestMode + from .topics_related_request_order import TopicsRelatedRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".channels_related_request_is_appearance", + "ChannelsRelatedRequestMode": ".channels_related_request_mode", + "ChannelsRelatedRequestOrder": ".channels_related_request_order", + "OrganizationsRelatedRequestIsAppearance": ".organizations_related_request_is_appearance", + "OrganizationsRelatedRequestMode": ".organizations_related_request_mode", + "OrganizationsRelatedRequestOrder": ".organizations_related_request_order", + "PeopleRelatedRequestIsAppearance": ".people_related_request_is_appearance", + "PeopleRelatedRequestMode": ".people_related_request_mode", + "PeopleRelatedRequestOrder": ".people_related_request_order", + "ProductsRelatedRequestIsAppearance": ".products_related_request_is_appearance", + "ProductsRelatedRequestMode": ".products_related_request_mode", + "ProductsRelatedRequestOrder": ".products_related_request_order", + "TopicsRelatedRequestIsAppearance": ".topics_related_request_is_appearance", + "TopicsRelatedRequestMode": ".topics_related_request_mode", + "TopicsRelatedRequestOrder": ".topics_related_request_order", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/products/related/types/channels_related_request_is_appearance.py b/src/arcmira/products/related/types/channels_related_request_is_appearance.py new file mode 100644 index 0000000..e21cd2c --- /dev/null +++ b/src/arcmira/products/related/types/channels_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/products/related/types/channels_related_request_mode.py b/src/arcmira/products/related/types/channels_related_request_mode.py new file mode 100644 index 0000000..49a4137 --- /dev/null +++ b/src/arcmira/products/related/types/channels_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/products/related/types/channels_related_request_order.py b/src/arcmira/products/related/types/channels_related_request_order.py new file mode 100644 index 0000000..0f5b302 --- /dev/null +++ b/src/arcmira/products/related/types/channels_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/products/related/types/organizations_related_request_is_appearance.py b/src/arcmira/products/related/types/organizations_related_request_is_appearance.py new file mode 100644 index 0000000..2762d60 --- /dev/null +++ b/src/arcmira/products/related/types/organizations_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/products/related/types/organizations_related_request_mode.py b/src/arcmira/products/related/types/organizations_related_request_mode.py new file mode 100644 index 0000000..2bdd64b --- /dev/null +++ b/src/arcmira/products/related/types/organizations_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/products/related/types/organizations_related_request_order.py b/src/arcmira/products/related/types/organizations_related_request_order.py new file mode 100644 index 0000000..4c5dde2 --- /dev/null +++ b/src/arcmira/products/related/types/organizations_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/products/related/types/people_related_request_is_appearance.py b/src/arcmira/products/related/types/people_related_request_is_appearance.py new file mode 100644 index 0000000..af591fc --- /dev/null +++ b/src/arcmira/products/related/types/people_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/products/related/types/people_related_request_mode.py b/src/arcmira/products/related/types/people_related_request_mode.py new file mode 100644 index 0000000..9d9b51c --- /dev/null +++ b/src/arcmira/products/related/types/people_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/products/related/types/people_related_request_order.py b/src/arcmira/products/related/types/people_related_request_order.py new file mode 100644 index 0000000..a0d19ad --- /dev/null +++ b/src/arcmira/products/related/types/people_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/products/related/types/products_related_request_is_appearance.py b/src/arcmira/products/related/types/products_related_request_is_appearance.py new file mode 100644 index 0000000..a6aba02 --- /dev/null +++ b/src/arcmira/products/related/types/products_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/products/related/types/products_related_request_mode.py b/src/arcmira/products/related/types/products_related_request_mode.py new file mode 100644 index 0000000..9046641 --- /dev/null +++ b/src/arcmira/products/related/types/products_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/products/related/types/products_related_request_order.py b/src/arcmira/products/related/types/products_related_request_order.py new file mode 100644 index 0000000..3e5eb9f --- /dev/null +++ b/src/arcmira/products/related/types/products_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/products/related/types/topics_related_request_is_appearance.py b/src/arcmira/products/related/types/topics_related_request_is_appearance.py new file mode 100644 index 0000000..2b7c65e --- /dev/null +++ b/src/arcmira/products/related/types/topics_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/products/related/types/topics_related_request_mode.py b/src/arcmira/products/related/types/topics_related_request_mode.py new file mode 100644 index 0000000..090ee69 --- /dev/null +++ b/src/arcmira/products/related/types/topics_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/products/related/types/topics_related_request_order.py b/src/arcmira/products/related/types/topics_related_request_order.py new file mode 100644 index 0000000..56c645a --- /dev/null +++ b/src/arcmira/products/related/types/topics_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/py.typed b/src/arcmira/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/arcmira/raw_client.py b/src/arcmira/raw_client.py new file mode 100644 index 0000000..c79ed87 --- /dev/null +++ b/src/arcmira/raw_client.py @@ -0,0 +1,294 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from .core.api_error import ApiError +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.http_response import AsyncHttpResponse, HttpResponse +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.internal_server_error import InternalServerError +from .errors.not_found_error import NotFoundError +from .errors.payment_required_error import PaymentRequiredError +from .errors.too_many_requests_error import TooManyRequestsError +from .errors.unauthorized_error import UnauthorizedError +from .types.error import Error +from .types.search_request_type import SearchRequestType +from .types.search_resolve_response import SearchResolveResponse +from pydantic import ValidationError + + +class RawArcmira: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def search( + self, + *, + q: str, + type: typing.Optional[SearchRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SearchResolveResponse]: + """ + Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. + + Parameters + ---------- + q : str + Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. + + type : typing.Optional[SearchRequestType] + Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SearchResolveResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/search", + method="GET", + params={ + "q": q, + "type": type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SearchResolveResponse, + parse_obj_as( + type_=SearchResolveResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawArcmira: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def search( + self, + *, + q: str, + type: typing.Optional[SearchRequestType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SearchResolveResponse]: + """ + Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. + + Parameters + ---------- + q : str + Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. + + type : typing.Optional[SearchRequestType] + Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SearchResolveResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/search", + method="GET", + params={ + "q": q, + "type": type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SearchResolveResponse, + parse_obj_as( + type_=SearchResolveResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/recommendations/__init__.py b/src/arcmira/recommendations/__init__.py new file mode 100644 index 0000000..782a20a --- /dev/null +++ b/src/arcmira/recommendations/__init__.py @@ -0,0 +1,46 @@ +# 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 ( + ListRecommendationsRequestEntityType, + ListRecommendationsRequestMentionClass, + ListRecommendationsRequestSrc, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ListRecommendationsRequestEntityType": ".types", + "ListRecommendationsRequestMentionClass": ".types", + "ListRecommendationsRequestSrc": ".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__ = [ + "ListRecommendationsRequestEntityType", + "ListRecommendationsRequestMentionClass", + "ListRecommendationsRequestSrc", +] diff --git a/src/arcmira/recommendations/client.py b/src/arcmira/recommendations/client.py new file mode 100644 index 0000000..0aa41e6 --- /dev/null +++ b/src/arcmira/recommendations/client.py @@ -0,0 +1,234 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pagination import AsyncPager, SyncPager +from ..core.request_options import RequestOptions +from ..types.recommendation import Recommendation +from ..types.recommendation_list_response import RecommendationListResponse +from .raw_client import AsyncRawRecommendationsClient, RawRecommendationsClient +from .types.list_recommendations_request_entity_type import ListRecommendationsRequestEntityType +from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass +from .types.list_recommendations_request_src import ListRecommendationsRequestSrc + + +class RecommendationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRecommendationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRecommendationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRecommendationsClient + """ + return self._raw_client + + def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListRecommendationsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListRecommendationsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Recommendation, RecommendationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.recommendations.list() + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list( + limit=limit, + cursor=cursor, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) + + +class AsyncRecommendationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRecommendationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRecommendationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRecommendationsClient + """ + return self._raw_client + + async def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListRecommendationsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListRecommendationsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Recommendation, RecommendationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.recommendations.list() + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list( + limit=limit, + cursor=cursor, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) diff --git a/src/arcmira/recommendations/raw_client.py b/src/arcmira/recommendations/raw_client.py new file mode 100644 index 0000000..3255f97 --- /dev/null +++ b/src/arcmira/recommendations/raw_client.py @@ -0,0 +1,426 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.recommendation import Recommendation +from ..types.recommendation_list_response import RecommendationListResponse +from .types.list_recommendations_request_entity_type import ListRecommendationsRequestEntityType +from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass +from .types.list_recommendations_request_src import ListRecommendationsRequestSrc +from pydantic import ValidationError + + +class RawRecommendationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListRecommendationsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListRecommendationsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[Recommendation, RecommendationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/recommendations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "entity_id": entity_id, + "entity_name": entity_name, + "entity_type": entity_type, + "channel_id": channel_id, + "channel_name": channel_name, + "mention_class": mention_class, + "min_confidence": min_confidence, + "date_from": date_from, + "date_to": date_to, + "include_disputed": include_disputed, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + RecommendationListResponse, + parse_obj_as( + type_=RecommendationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + limit=limit, + cursor=_parsed_next, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRecommendationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + entity_id: typing.Optional[str] = None, + entity_name: typing.Optional[str] = None, + entity_type: typing.Optional[ListRecommendationsRequestEntityType] = None, + channel_id: typing.Optional[str] = None, + channel_name: typing.Optional[str] = None, + mention_class: typing.Optional[ListRecommendationsRequestMentionClass] = None, + min_confidence: typing.Optional[float] = None, + date_from: typing.Optional[str] = None, + date_to: typing.Optional[str] = None, + include_disputed: typing.Optional[bool] = None, + src: typing.Optional[ListRecommendationsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[Recommendation, RecommendationListResponse]: + """ + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + + Parameters + ---------- + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to this route, normalized query, caller and visibility; invalid or old tokens return invalid_cursor. + + entity_id : typing.Optional[str] + + entity_name : typing.Optional[str] + + entity_type : typing.Optional[ListRecommendationsRequestEntityType] + + channel_id : typing.Optional[str] + + channel_name : typing.Optional[str] + + mention_class : typing.Optional[ListRecommendationsRequestMentionClass] + + min_confidence : typing.Optional[float] + + date_from : typing.Optional[str] + + date_to : typing.Optional[str] + + include_disputed : typing.Optional[bool] + + src : typing.Optional[ListRecommendationsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[Recommendation, RecommendationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/recommendations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "entity_id": entity_id, + "entity_name": entity_name, + "entity_type": entity_type, + "channel_id": channel_id, + "channel_name": channel_name, + "mention_class": mention_class, + "min_confidence": min_confidence, + "date_from": date_from, + "date_to": date_to, + "include_disputed": include_disputed, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + RecommendationListResponse, + parse_obj_as( + type_=RecommendationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + limit=limit, + cursor=_parsed_next, + entity_id=entity_id, + entity_name=entity_name, + entity_type=entity_type, + channel_id=channel_id, + channel_name=channel_name, + mention_class=mention_class, + min_confidence=min_confidence, + date_from=date_from, + date_to=date_to, + include_disputed=include_disputed, + src=src, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/recommendations/types/__init__.py b/src/arcmira/recommendations/types/__init__.py new file mode 100644 index 0000000..5a56dd0 --- /dev/null +++ b/src/arcmira/recommendations/types/__init__.py @@ -0,0 +1,44 @@ +# 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 .list_recommendations_request_entity_type import ListRecommendationsRequestEntityType + from .list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass + from .list_recommendations_request_src import ListRecommendationsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "ListRecommendationsRequestEntityType": ".list_recommendations_request_entity_type", + "ListRecommendationsRequestMentionClass": ".list_recommendations_request_mention_class", + "ListRecommendationsRequestSrc": ".list_recommendations_request_src", +} + + +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__ = [ + "ListRecommendationsRequestEntityType", + "ListRecommendationsRequestMentionClass", + "ListRecommendationsRequestSrc", +] diff --git a/src/arcmira/recommendations/types/list_recommendations_request_entity_type.py b/src/arcmira/recommendations/types/list_recommendations_request_entity_type.py new file mode 100644 index 0000000..2dc9a73 --- /dev/null +++ b/src/arcmira/recommendations/types/list_recommendations_request_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListRecommendationsRequestEntityType = typing.Union[ + typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any +] diff --git a/src/arcmira/recommendations/types/list_recommendations_request_mention_class.py b/src/arcmira/recommendations/types/list_recommendations_request_mention_class.py new file mode 100644 index 0000000..a106d25 --- /dev/null +++ b/src/arcmira/recommendations/types/list_recommendations_request_mention_class.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListRecommendationsRequestMentionClass = typing.Union[ + typing.Literal["ad_read", "endorsement", "mention", "all"], typing.Any +] diff --git a/src/arcmira/recommendations/types/list_recommendations_request_src.py b/src/arcmira/recommendations/types/list_recommendations_request_src.py new file mode 100644 index 0000000..a8ce855 --- /dev/null +++ b/src/arcmira/recommendations/types/list_recommendations_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListRecommendationsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/team/__init__.py b/src/arcmira/team/__init__.py new file mode 100644 index 0000000..44c445d --- /dev/null +++ b/src/arcmira/team/__init__.py @@ -0,0 +1,34 @@ +# 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 . import usage_events +_dynamic_imports: typing.Dict[str, str] = {"usage_events": ".usage_events"} + + +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__ = ["usage_events"] diff --git a/src/arcmira/team/client.py b/src/arcmira/team/client.py new file mode 100644 index 0000000..10786b4 --- /dev/null +++ b/src/arcmira/team/client.py @@ -0,0 +1,186 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.team_members_response import TeamMembersResponse +from ..types.team_spend_response import TeamSpendResponse +from .raw_client import AsyncRawTeamClient, RawTeamClient + +if typing.TYPE_CHECKING: + from .usage_events.client import AsyncUsageEventsClient, UsageEventsClient + + +class TeamClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTeamClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._usage_events: typing.Optional[UsageEventsClient] = None + + @property + def with_raw_response(self) -> RawTeamClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTeamClient + """ + return self._raw_client + + def members(self, *, request_options: typing.Optional[RequestOptions] = None) -> TeamMembersResponse: + """ + Active members of the team the key is scoped to, with role and seat type, earliest join first. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TeamMembersResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.team.members() + """ + _response = self._raw_client.members(request_options=request_options) + return _response.data + + def spend(self, *, request_options: typing.Optional[RequestOptions] = None) -> TeamSpendResponse: + """ + Account-wide rows consumed and on-demand overage spend for every active member in the current period. Totals include personal-key use and activity before joining; they are not a team-attributed invoice. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TeamSpendResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.team.spend() + """ + _response = self._raw_client.spend(request_options=request_options) + return _response.data + + @property + def usage_events(self): + if self._usage_events is None: + from .usage_events.client import UsageEventsClient # noqa: E402 + + self._usage_events = UsageEventsClient(client_wrapper=self._client_wrapper) + return self._usage_events + + +class AsyncTeamClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTeamClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._usage_events: typing.Optional[AsyncUsageEventsClient] = None + + @property + def with_raw_response(self) -> AsyncRawTeamClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTeamClient + """ + return self._raw_client + + async def members(self, *, request_options: typing.Optional[RequestOptions] = None) -> TeamMembersResponse: + """ + Active members of the team the key is scoped to, with role and seat type, earliest join first. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TeamMembersResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.team.members() + + + asyncio.run(main()) + """ + _response = await self._raw_client.members(request_options=request_options) + return _response.data + + async def spend(self, *, request_options: typing.Optional[RequestOptions] = None) -> TeamSpendResponse: + """ + Account-wide rows consumed and on-demand overage spend for every active member in the current period. Totals include personal-key use and activity before joining; they are not a team-attributed invoice. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TeamSpendResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.team.spend() + + + asyncio.run(main()) + """ + _response = await self._raw_client.spend(request_options=request_options) + return _response.data + + @property + def usage_events(self): + if self._usage_events is None: + from .usage_events.client import AsyncUsageEventsClient # noqa: E402 + + self._usage_events = AsyncUsageEventsClient(client_wrapper=self._client_wrapper) + return self._usage_events diff --git a/src/arcmira/team/raw_client.py b/src/arcmira/team/raw_client.py new file mode 100644 index 0000000..20044a9 --- /dev/null +++ b/src/arcmira/team/raw_client.py @@ -0,0 +1,451 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.team_members_response import TeamMembersResponse +from ..types.team_spend_response import TeamSpendResponse +from pydantic import ValidationError + + +class RawTeamClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def members(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[TeamMembersResponse]: + """ + Active members of the team the key is scoped to, with role and seat type, earliest join first. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TeamMembersResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/team/members", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TeamMembersResponse, + parse_obj_as( + type_=TeamMembersResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 spend(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[TeamSpendResponse]: + """ + Account-wide rows consumed and on-demand overage spend for every active member in the current period. Totals include personal-key use and activity before joining; they are not a team-attributed invoice. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TeamSpendResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/team/spend", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TeamSpendResponse, + parse_obj_as( + type_=TeamSpendResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawTeamClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def members( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TeamMembersResponse]: + """ + Active members of the team the key is scoped to, with role and seat type, earliest join first. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TeamMembersResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/team/members", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TeamMembersResponse, + parse_obj_as( + type_=TeamMembersResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 spend( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TeamSpendResponse]: + """ + Account-wide rows consumed and on-demand overage spend for every active member in the current period. Totals include personal-key use and activity before joining; they are not a team-attributed invoice. Single page, no pagination. Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TeamSpendResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/team/spend", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TeamSpendResponse, + parse_obj_as( + type_=TeamSpendResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/team/usage_events/__init__.py b/src/arcmira/team/usage_events/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/team/usage_events/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/team/usage_events/client.py b/src/arcmira/team/usage_events/client.py new file mode 100644 index 0000000..a6af4d5 --- /dev/null +++ b/src/arcmira/team/usage_events/client.py @@ -0,0 +1,143 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.team_usage_event import TeamUsageEvent +from ...types.team_usage_events_response import TeamUsageEventsResponse +from .raw_client import AsyncRawUsageEventsClient, RawUsageEventsClient + + +class UsageEventsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawUsageEventsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawUsageEventsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawUsageEventsClient + """ + return self._raw_client + + def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + days: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[TeamUsageEvent, TeamUsageEventsResponse]: + """ + Account usage log across every active team member, including personal-key use and activity before joining. Ordered by created_at and id descending, bounded to a 90-day look-back. Continuation preserves the first page's time window and excludes subsequently inserted events, including backfills. Removed members and deleted events disappear during traversal; this is not a historical membership snapshot. Cursors expire after 24 hours and bind the team, caller, days and limit; invalid or changed-query cursors return invalid_cursor rather than restarting. The aggregated analytics chart data is not exposed on this API (Enterprise). Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + limit : typing.Optional[int] + Events per page, 1 to 100. + + cursor : typing.Optional[str] + Opaque cursor from a previous page's next_cursor. + + days : typing.Optional[int] + Look-back window in days, bounded at 90. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[TeamUsageEvent, TeamUsageEventsResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.team.usage_events.list() + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list(limit=limit, cursor=cursor, days=days, request_options=request_options) + + +class AsyncUsageEventsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawUsageEventsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawUsageEventsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawUsageEventsClient + """ + return self._raw_client + + async def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + days: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[TeamUsageEvent, TeamUsageEventsResponse]: + """ + Account usage log across every active team member, including personal-key use and activity before joining. Ordered by created_at and id descending, bounded to a 90-day look-back. Continuation preserves the first page's time window and excludes subsequently inserted events, including backfills. Removed members and deleted events disappear during traversal; this is not a historical membership snapshot. Cursors expire after 24 hours and bind the team, caller, days and limit; invalid or changed-query cursors return invalid_cursor rather than restarting. The aggregated analytics chart data is not exposed on this API (Enterprise). Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + limit : typing.Optional[int] + Events per page, 1 to 100. + + cursor : typing.Optional[str] + Opaque cursor from a previous page's next_cursor. + + days : typing.Optional[int] + Look-back window in days, bounded at 90. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[TeamUsageEvent, TeamUsageEventsResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.team.usage_events.list() + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list(limit=limit, cursor=cursor, days=days, request_options=request_options) diff --git a/src/arcmira/team/usage_events/raw_client.py b/src/arcmira/team/usage_events/raw_client.py new file mode 100644 index 0000000..5512fc9 --- /dev/null +++ b/src/arcmira/team/usage_events/raw_client.py @@ -0,0 +1,302 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.team_usage_event import TeamUsageEvent +from ...types.team_usage_events_response import TeamUsageEventsResponse +from pydantic import ValidationError + + +class RawUsageEventsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + days: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[TeamUsageEvent, TeamUsageEventsResponse]: + """ + Account usage log across every active team member, including personal-key use and activity before joining. Ordered by created_at and id descending, bounded to a 90-day look-back. Continuation preserves the first page's time window and excludes subsequently inserted events, including backfills. Removed members and deleted events disappear during traversal; this is not a historical membership snapshot. Cursors expire after 24 hours and bind the team, caller, days and limit; invalid or changed-query cursors return invalid_cursor rather than restarting. The aggregated analytics chart data is not exposed on this API (Enterprise). Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + limit : typing.Optional[int] + Events per page, 1 to 100. + + cursor : typing.Optional[str] + Opaque cursor from a previous page's next_cursor. + + days : typing.Optional[int] + Look-back window in days, bounded at 90. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[TeamUsageEvent, TeamUsageEventsResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/team/usage-events", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "days": days, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + TeamUsageEventsResponse, + parse_obj_as( + type_=TeamUsageEventsResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list( + limit=limit, + cursor=_parsed_next, + days=days, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawUsageEventsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + days: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[TeamUsageEvent, TeamUsageEventsResponse]: + """ + Account usage log across every active team member, including personal-key use and activity before joining. Ordered by created_at and id descending, bounded to a 90-day look-back. Continuation preserves the first page's time window and excludes subsequently inserted events, including backfills. Removed members and deleted events disappear during traversal; this is not a historical membership snapshot. Cursors expire after 24 hours and bind the team, caller, days and limit; invalid or changed-query cursors return invalid_cursor rather than restarting. The aggregated analytics chart data is not exposed on this API (Enterprise). Requires a team-scoped API key whose owner is still an active team admin. Personal keys and keys whose owner lost team authority receive 403 (team_key_required). + + Parameters + ---------- + limit : typing.Optional[int] + Events per page, 1 to 100. + + cursor : typing.Optional[str] + Opaque cursor from a previous page's next_cursor. + + days : typing.Optional[int] + Look-back window in days, bounded at 90. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[TeamUsageEvent, TeamUsageEventsResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/team/usage-events", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "days": days, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + TeamUsageEventsResponse, + parse_obj_as( + type_=TeamUsageEventsResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.data + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list( + limit=limit, + cursor=_parsed_next, + days=days, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/topics/__init__.py b/src/arcmira/topics/__init__.py new file mode 100644 index 0000000..c556990 --- /dev/null +++ b/src/arcmira/topics/__init__.py @@ -0,0 +1,85 @@ +# 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 . import related + from .related import ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".related", + "ChannelsRelatedRequestMode": ".related", + "ChannelsRelatedRequestOrder": ".related", + "OrganizationsRelatedRequestIsAppearance": ".related", + "OrganizationsRelatedRequestMode": ".related", + "OrganizationsRelatedRequestOrder": ".related", + "PeopleRelatedRequestIsAppearance": ".related", + "PeopleRelatedRequestMode": ".related", + "PeopleRelatedRequestOrder": ".related", + "ProductsRelatedRequestIsAppearance": ".related", + "ProductsRelatedRequestMode": ".related", + "ProductsRelatedRequestOrder": ".related", + "TopicsRelatedRequestIsAppearance": ".related", + "TopicsRelatedRequestMode": ".related", + "TopicsRelatedRequestOrder": ".related", + "related": ".related", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", + "related", +] diff --git a/src/arcmira/topics/client.py b/src/arcmira/topics/client.py new file mode 100644 index 0000000..c5ebbcc --- /dev/null +++ b/src/arcmira/topics/client.py @@ -0,0 +1,131 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.topic_page_response import TopicPageResponse +from .raw_client import AsyncRawTopicsClient, RawTopicsClient + +if typing.TYPE_CHECKING: + from .related.client import AsyncRelatedClient, RelatedClient + + +class TopicsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTopicsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._related: typing.Optional[RelatedClient] = None + + @property + def with_raw_response(self) -> RawTopicsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTopicsClient + """ + return self._raw_client + + def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> TopicPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicPageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.topics.get( + slug="slug", + ) + """ + _response = self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def related(self): + if self._related is None: + from .related.client import RelatedClient # noqa: E402 + + self._related = RelatedClient(client_wrapper=self._client_wrapper) + return self._related + + +class AsyncTopicsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTopicsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._related: typing.Optional[AsyncRelatedClient] = None + + @property + def with_raw_response(self) -> AsyncRawTopicsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTopicsClient + """ + return self._raw_client + + async def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> TopicPageResponse: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicPageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.topics.get( + slug="slug", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(slug, request_options=request_options) + return _response.data + + @property + def related(self): + if self._related is None: + from .related.client import AsyncRelatedClient # noqa: E402 + + self._related = AsyncRelatedClient(client_wrapper=self._client_wrapper) + return self._related diff --git a/src/arcmira/topics/raw_client.py b/src/arcmira/topics/raw_client.py new file mode 100644 index 0000000..6416ad4 --- /dev/null +++ b/src/arcmira/topics/raw_client.py @@ -0,0 +1,268 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.topic_page_response import TopicPageResponse +from pydantic import ValidationError + + +class RawTopicsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TopicPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicPageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicPageResponse, + parse_obj_as( + type_=TopicPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawTopicsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, slug: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TopicPageResponse]: + """ + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicPageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicPageResponse, + parse_obj_as( + type_=TopicPageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/topics/related/__init__.py b/src/arcmira/topics/related/__init__.py new file mode 100644 index 0000000..bed85d9 --- /dev/null +++ b/src/arcmira/topics/related/__init__.py @@ -0,0 +1,82 @@ +# 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 ( + ChannelsRelatedRequestIsAppearance, + ChannelsRelatedRequestMode, + ChannelsRelatedRequestOrder, + OrganizationsRelatedRequestIsAppearance, + OrganizationsRelatedRequestMode, + OrganizationsRelatedRequestOrder, + PeopleRelatedRequestIsAppearance, + PeopleRelatedRequestMode, + PeopleRelatedRequestOrder, + ProductsRelatedRequestIsAppearance, + ProductsRelatedRequestMode, + ProductsRelatedRequestOrder, + TopicsRelatedRequestIsAppearance, + TopicsRelatedRequestMode, + TopicsRelatedRequestOrder, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".types", + "ChannelsRelatedRequestMode": ".types", + "ChannelsRelatedRequestOrder": ".types", + "OrganizationsRelatedRequestIsAppearance": ".types", + "OrganizationsRelatedRequestMode": ".types", + "OrganizationsRelatedRequestOrder": ".types", + "PeopleRelatedRequestIsAppearance": ".types", + "PeopleRelatedRequestMode": ".types", + "PeopleRelatedRequestOrder": ".types", + "ProductsRelatedRequestIsAppearance": ".types", + "ProductsRelatedRequestMode": ".types", + "ProductsRelatedRequestOrder": ".types", + "TopicsRelatedRequestIsAppearance": ".types", + "TopicsRelatedRequestMode": ".types", + "TopicsRelatedRequestOrder": ".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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/topics/related/client.py b/src/arcmira/topics/related/client.py new file mode 100644 index 0000000..9af6a28 --- /dev/null +++ b/src/arcmira/topics/related/client.py @@ -0,0 +1,930 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pagination import AsyncPager, SyncPager +from ...core.request_options import RequestOptions +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from .raw_client import AsyncRawRelatedClient, RawRelatedClient +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder + + +class RelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRelatedClient + """ + return self._raw_client + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.topics.related.topics( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.topics.related.people( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.topics.related.organizations( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.topics.related.products( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.topics.related.channels( + slug="slug", + ) + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + +class AsyncRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRelatedClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRelatedClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRelatedClient + """ + return self._raw_client + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.topics.related.topics( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.topics( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.topics.related.people( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.people( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.topics.related.organizations( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.organizations( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.topics.related.products( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.products( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + async def channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.topics.related.channels( + slug="slug", + ) + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.channels( + slug, + limit=limit, + cursor=cursor, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) diff --git a/src/arcmira/topics/related/raw_client.py b/src/arcmira/topics/related/raw_client.py new file mode 100644 index 0000000..1511f2a --- /dev/null +++ b/src/arcmira/topics/related/raw_client.py @@ -0,0 +1,1861 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import encode_path_param +from ...core.pagination import AsyncPager, SyncPager +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.payment_required_error import PaymentRequiredError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.entity_channel_list_response import EntityChannelListResponse +from ...types.entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from ...types.entity_organization_list_response import EntityOrganizationListResponse +from ...types.entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from ...types.entity_people_list_response import EntityPeopleListResponse +from ...types.entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from ...types.entity_product_list_response import EntityProductListResponse +from ...types.entity_product_list_response_items_item import EntityProductListResponseItemsItem +from ...types.entity_topic_list_response import EntityTopicListResponse +from ...types.entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from ...types.error import Error +from .types.channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance +from .types.channels_related_request_mode import ChannelsRelatedRequestMode +from .types.channels_related_request_order import ChannelsRelatedRequestOrder +from .types.organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance +from .types.organizations_related_request_mode import OrganizationsRelatedRequestMode +from .types.organizations_related_request_order import OrganizationsRelatedRequestOrder +from .types.people_related_request_is_appearance import PeopleRelatedRequestIsAppearance +from .types.people_related_request_mode import PeopleRelatedRequestMode +from .types.people_related_request_order import PeopleRelatedRequestOrder +from .types.products_related_request_is_appearance import ProductsRelatedRequestIsAppearance +from .types.products_related_request_mode import ProductsRelatedRequestMode +from .types.products_related_request_order import ProductsRelatedRequestOrder +from .types.topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance +from .types.topics_related_request_mode import TopicsRelatedRequestMode +from .types.topics_related_request_order import TopicsRelatedRequestOrder +from pydantic import ValidationError + + +class RawRelatedClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawRelatedClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def topics( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[TopicsRelatedRequestOrder] = None, + mode: typing.Optional[TopicsRelatedRequestMode] = None, + is_appearance: typing.Optional[TopicsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: + """ + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[TopicsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[TopicsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[TopicsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/topics", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityTopicListResponse, + parse_obj_as( + type_=EntityTopicListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.topics( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 people( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[PeopleRelatedRequestOrder] = None, + mode: typing.Optional[PeopleRelatedRequestMode] = None, + is_appearance: typing.Optional[PeopleRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: + """ + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[PeopleRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[PeopleRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[PeopleRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/people", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityPeopleListResponse, + parse_obj_as( + type_=EntityPeopleListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.people( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 organizations( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[OrganizationsRelatedRequestOrder] = None, + mode: typing.Optional[OrganizationsRelatedRequestMode] = None, + is_appearance: typing.Optional[OrganizationsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: + """ + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[OrganizationsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[OrganizationsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[OrganizationsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/organizations", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityOrganizationListResponse, + parse_obj_as( + type_=EntityOrganizationListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.organizations( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 products( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ProductsRelatedRequestOrder] = None, + mode: typing.Optional[ProductsRelatedRequestMode] = None, + is_appearance: typing.Optional[ProductsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: + """ + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ProductsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ProductsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ProductsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/products", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityProductListResponse, + parse_obj_as( + type_=EntityProductListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.products( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 channels( + self, + slug: str, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + q: typing.Optional[str] = None, + field: typing.Optional[str] = None, + sort: typing.Optional[str] = None, + order: typing.Optional[ChannelsRelatedRequestOrder] = None, + mode: typing.Optional[ChannelsRelatedRequestMode] = None, + is_appearance: typing.Optional[ChannelsRelatedRequestIsAppearance] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: + """ + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + + Parameters + ---------- + slug : str + The entity slug: the last segment of its arcmira.com page URL, as EntityRef.slug carries it. + + limit : typing.Optional[int] + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + + q : typing.Optional[str] + Substring filter over the row's text columns (e.g. video title, channel name, description). + + field : typing.Optional[str] + Restrict the q filter to one column. Default "any" (all searchable columns). + + sort : typing.Optional[str] + Sort key. Rows default to newest first; supported values vary by list (e.g. "date", "channel"). + + order : typing.Optional[ChannelsRelatedRequestOrder] + Sort direction. Default desc. + + mode : typing.Optional[ChannelsRelatedRequestMode] + Person relationship lens: guest appearances or inbound mentions. + + is_appearance : typing.Optional[ChannelsRelatedRequestIsAppearance] + For person appearances, true lists guest episodes and false lists inbound mentions. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/topics/{encode_path_param(slug)}/channels", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "q": q, + "field": field, + "sort": sort, + "order": order, + "mode": mode, + "is_appearance": is_appearance, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + EntityChannelListResponse, + parse_obj_as( + type_=EntityChannelListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.items + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.channels( + slug, + limit=limit, + cursor=_parsed_next, + q=q, + field=field, + sort=sort, + order=order, + mode=mode, + is_appearance=is_appearance, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/topics/related/types/__init__.py b/src/arcmira/topics/related/types/__init__.py new file mode 100644 index 0000000..1f3a8a7 --- /dev/null +++ b/src/arcmira/topics/related/types/__init__.py @@ -0,0 +1,80 @@ +# 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 .channels_related_request_is_appearance import ChannelsRelatedRequestIsAppearance + from .channels_related_request_mode import ChannelsRelatedRequestMode + from .channels_related_request_order import ChannelsRelatedRequestOrder + from .organizations_related_request_is_appearance import OrganizationsRelatedRequestIsAppearance + from .organizations_related_request_mode import OrganizationsRelatedRequestMode + from .organizations_related_request_order import OrganizationsRelatedRequestOrder + from .people_related_request_is_appearance import PeopleRelatedRequestIsAppearance + from .people_related_request_mode import PeopleRelatedRequestMode + from .people_related_request_order import PeopleRelatedRequestOrder + from .products_related_request_is_appearance import ProductsRelatedRequestIsAppearance + from .products_related_request_mode import ProductsRelatedRequestMode + from .products_related_request_order import ProductsRelatedRequestOrder + from .topics_related_request_is_appearance import TopicsRelatedRequestIsAppearance + from .topics_related_request_mode import TopicsRelatedRequestMode + from .topics_related_request_order import TopicsRelatedRequestOrder +_dynamic_imports: typing.Dict[str, str] = { + "ChannelsRelatedRequestIsAppearance": ".channels_related_request_is_appearance", + "ChannelsRelatedRequestMode": ".channels_related_request_mode", + "ChannelsRelatedRequestOrder": ".channels_related_request_order", + "OrganizationsRelatedRequestIsAppearance": ".organizations_related_request_is_appearance", + "OrganizationsRelatedRequestMode": ".organizations_related_request_mode", + "OrganizationsRelatedRequestOrder": ".organizations_related_request_order", + "PeopleRelatedRequestIsAppearance": ".people_related_request_is_appearance", + "PeopleRelatedRequestMode": ".people_related_request_mode", + "PeopleRelatedRequestOrder": ".people_related_request_order", + "ProductsRelatedRequestIsAppearance": ".products_related_request_is_appearance", + "ProductsRelatedRequestMode": ".products_related_request_mode", + "ProductsRelatedRequestOrder": ".products_related_request_order", + "TopicsRelatedRequestIsAppearance": ".topics_related_request_is_appearance", + "TopicsRelatedRequestMode": ".topics_related_request_mode", + "TopicsRelatedRequestOrder": ".topics_related_request_order", +} + + +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__ = [ + "ChannelsRelatedRequestIsAppearance", + "ChannelsRelatedRequestMode", + "ChannelsRelatedRequestOrder", + "OrganizationsRelatedRequestIsAppearance", + "OrganizationsRelatedRequestMode", + "OrganizationsRelatedRequestOrder", + "PeopleRelatedRequestIsAppearance", + "PeopleRelatedRequestMode", + "PeopleRelatedRequestOrder", + "ProductsRelatedRequestIsAppearance", + "ProductsRelatedRequestMode", + "ProductsRelatedRequestOrder", + "TopicsRelatedRequestIsAppearance", + "TopicsRelatedRequestMode", + "TopicsRelatedRequestOrder", +] diff --git a/src/arcmira/topics/related/types/channels_related_request_is_appearance.py b/src/arcmira/topics/related/types/channels_related_request_is_appearance.py new file mode 100644 index 0000000..e21cd2c --- /dev/null +++ b/src/arcmira/topics/related/types/channels_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/topics/related/types/channels_related_request_mode.py b/src/arcmira/topics/related/types/channels_related_request_mode.py new file mode 100644 index 0000000..49a4137 --- /dev/null +++ b/src/arcmira/topics/related/types/channels_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/topics/related/types/channels_related_request_order.py b/src/arcmira/topics/related/types/channels_related_request_order.py new file mode 100644 index 0000000..0f5b302 --- /dev/null +++ b/src/arcmira/topics/related/types/channels_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/topics/related/types/organizations_related_request_is_appearance.py b/src/arcmira/topics/related/types/organizations_related_request_is_appearance.py new file mode 100644 index 0000000..2762d60 --- /dev/null +++ b/src/arcmira/topics/related/types/organizations_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/topics/related/types/organizations_related_request_mode.py b/src/arcmira/topics/related/types/organizations_related_request_mode.py new file mode 100644 index 0000000..2bdd64b --- /dev/null +++ b/src/arcmira/topics/related/types/organizations_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/topics/related/types/organizations_related_request_order.py b/src/arcmira/topics/related/types/organizations_related_request_order.py new file mode 100644 index 0000000..4c5dde2 --- /dev/null +++ b/src/arcmira/topics/related/types/organizations_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/topics/related/types/people_related_request_is_appearance.py b/src/arcmira/topics/related/types/people_related_request_is_appearance.py new file mode 100644 index 0000000..af591fc --- /dev/null +++ b/src/arcmira/topics/related/types/people_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/topics/related/types/people_related_request_mode.py b/src/arcmira/topics/related/types/people_related_request_mode.py new file mode 100644 index 0000000..9d9b51c --- /dev/null +++ b/src/arcmira/topics/related/types/people_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/topics/related/types/people_related_request_order.py b/src/arcmira/topics/related/types/people_related_request_order.py new file mode 100644 index 0000000..a0d19ad --- /dev/null +++ b/src/arcmira/topics/related/types/people_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PeopleRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/topics/related/types/products_related_request_is_appearance.py b/src/arcmira/topics/related/types/products_related_request_is_appearance.py new file mode 100644 index 0000000..a6aba02 --- /dev/null +++ b/src/arcmira/topics/related/types/products_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/topics/related/types/products_related_request_mode.py b/src/arcmira/topics/related/types/products_related_request_mode.py new file mode 100644 index 0000000..9046641 --- /dev/null +++ b/src/arcmira/topics/related/types/products_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/topics/related/types/products_related_request_order.py b/src/arcmira/topics/related/types/products_related_request_order.py new file mode 100644 index 0000000..3e5eb9f --- /dev/null +++ b/src/arcmira/topics/related/types/products_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/topics/related/types/topics_related_request_is_appearance.py b/src/arcmira/topics/related/types/topics_related_request_is_appearance.py new file mode 100644 index 0000000..2b7c65e --- /dev/null +++ b/src/arcmira/topics/related/types/topics_related_request_is_appearance.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestIsAppearance = typing.Union[typing.Literal["0", "1", "true", "false"], typing.Any] diff --git a/src/arcmira/topics/related/types/topics_related_request_mode.py b/src/arcmira/topics/related/types/topics_related_request_mode.py new file mode 100644 index 0000000..090ee69 --- /dev/null +++ b/src/arcmira/topics/related/types/topics_related_request_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/topics/related/types/topics_related_request_order.py b/src/arcmira/topics/related/types/topics_related_request_order.py new file mode 100644 index 0000000..56c645a --- /dev/null +++ b/src/arcmira/topics/related/types/topics_related_request_order.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicsRelatedRequestOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/arcmira/trackers/__init__.py b/src/arcmira/trackers/__init__.py new file mode 100644 index 0000000..1ed1b6a --- /dev/null +++ b/src/arcmira/trackers/__init__.py @@ -0,0 +1,49 @@ +# 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 ( + CreateTrackersRequestEntityType, + CreateTrackersRequestPersonMatchMode, + UpdateTrackersRequestPersonMatchMode, + ) + from . import alerts +_dynamic_imports: typing.Dict[str, str] = { + "CreateTrackersRequestEntityType": ".types", + "CreateTrackersRequestPersonMatchMode": ".types", + "UpdateTrackersRequestPersonMatchMode": ".types", + "alerts": ".alerts", +} + + +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__ = [ + "CreateTrackersRequestEntityType", + "CreateTrackersRequestPersonMatchMode", + "UpdateTrackersRequestPersonMatchMode", + "alerts", +] diff --git a/src/arcmira/trackers/alerts/__init__.py b/src/arcmira/trackers/alerts/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/trackers/alerts/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/trackers/alerts/client.py b/src/arcmira/trackers/alerts/client.py new file mode 100644 index 0000000..e358a35 --- /dev/null +++ b/src/arcmira/trackers/alerts/client.py @@ -0,0 +1,118 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.alert_list_response import AlertListResponse +from .raw_client import AsyncRawAlertsClient, RawAlertsClient + + +class AlertsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawAlertsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawAlertsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawAlertsClient + """ + return self._raw_client + + def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AlertListResponse: + """ + The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AlertListResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.trackers.alerts.list( + id="id", + ) + """ + _response = self._raw_client.list(id, n=n, request_options=request_options) + return _response.data + + +class AsyncAlertsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawAlertsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawAlertsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawAlertsClient + """ + return self._raw_client + + async def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AlertListResponse: + """ + The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AlertListResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.trackers.alerts.list( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(id, n=n, request_options=request_options) + return _response.data diff --git a/src/arcmira/trackers/alerts/raw_client.py b/src/arcmira/trackers/alerts/raw_client.py new file mode 100644 index 0000000..6d61f44 --- /dev/null +++ b/src/arcmira/trackers/alerts/raw_client.py @@ -0,0 +1,259 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.alert_list_response import AlertListResponse +from ...types.error import Error +from pydantic import ValidationError + + +class RawAlertsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[AlertListResponse]: + """ + The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AlertListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/trackers/{encode_path_param(id)}/alerts", + method="GET", + params={ + "n": n, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AlertListResponse, + parse_obj_as( + type_=AlertListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawAlertsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[AlertListResponse]: + """ + The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + n : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AlertListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/trackers/{encode_path_param(id)}/alerts", + method="GET", + params={ + "n": n, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AlertListResponse, + parse_obj_as( + type_=AlertListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/trackers/client.py b/src/arcmira/trackers/client.py new file mode 100644 index 0000000..8201f24 --- /dev/null +++ b/src/arcmira/trackers/client.py @@ -0,0 +1,614 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.message_response import MessageResponse +from ..types.tracker_list_response import TrackerListResponse +from ..types.tracker_mutation_response import TrackerMutationResponse +from .raw_client import AsyncRawTrackersClient, RawTrackersClient +from .types.create_trackers_request_entity_type import CreateTrackersRequestEntityType +from .types.create_trackers_request_person_match_mode import CreateTrackersRequestPersonMatchMode +from .types.update_trackers_request_person_match_mode import UpdateTrackersRequestPersonMatchMode + +if typing.TYPE_CHECKING: + from .alerts.client import AlertsClient, AsyncAlertsClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class TrackersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTrackersClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._alerts: typing.Optional[AlertsClient] = None + + @property + def with_raw_response(self) -> RawTrackersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTrackersClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> TrackerListResponse: + """ + All trackers for the account, newest first, with per-channel delivery counts for the current billing period. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TrackerListResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.trackers.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, + *, + entity_name: str, + entity_type: CreateTrackersRequestEntityType, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[CreateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TrackerMutationResponse: + """ + Creates a standalone tracker watching one entity (name + type, resolved with the same entity resolution Search uses). Attach it to a monitor afterwards via POST /v1/monitors/{id}/trackers. Creating a duplicate (same entity name + type) returns 409 with the existingId. + + Parameters + ---------- + entity_name : str + The entity name to resolve and watch. Required on create. Creating a duplicate (same name + type) returns 409 with the existingId. + + entity_type : CreateTrackersRequestEntityType + Entity type of the tracked entity. Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[CreateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TrackerMutationResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.trackers.create( + entity_name="entityName", + entity_type="person", + ) + """ + _response = self._raw_client.create( + entity_name=entity_name, + entity_type=entity_type, + idempotency_key=idempotency_key, + display_name=display_name, + notify_email=notify_email, + notify_webhook=notify_webhook, + notify_slack=notify_slack, + webhook_url=webhook_url, + slack_channel_id=slack_channel_id, + slack_integration_id=slack_integration_id, + person_match_mode=person_match_mode, + filters=filters, + request_options=request_options, + ) + return _response.data + + def delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MessageResponse: + """ + Deletes the tracker. Cannot be undone. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MessageResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.trackers.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, idempotency_key=idempotency_key, request_options=request_options) + return _response.data + + def update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[UpdateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + paused: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TrackerMutationResponse: + """ + Partial update: send only the fields to change. The tracked entity itself (entityName/entityType) is immutable; delete and recreate to watch a different entity. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[UpdateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + paused : typing.Optional[bool] + Pause or resume the tracker. Paused trackers stop producing alerts; there is no backfill for the paused window. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TrackerMutationResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.trackers.update( + id="id", + ) + """ + _response = self._raw_client.update( + id, + idempotency_key=idempotency_key, + display_name=display_name, + notify_email=notify_email, + notify_webhook=notify_webhook, + notify_slack=notify_slack, + webhook_url=webhook_url, + slack_channel_id=slack_channel_id, + slack_integration_id=slack_integration_id, + person_match_mode=person_match_mode, + filters=filters, + paused=paused, + request_options=request_options, + ) + return _response.data + + @property + def alerts(self): + if self._alerts is None: + from .alerts.client import AlertsClient # noqa: E402 + + self._alerts = AlertsClient(client_wrapper=self._client_wrapper) + return self._alerts + + +class AsyncTrackersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTrackersClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._alerts: typing.Optional[AsyncAlertsClient] = None + + @property + def with_raw_response(self) -> AsyncRawTrackersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTrackersClient + """ + return self._raw_client + + async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> TrackerListResponse: + """ + All trackers for the account, newest first, with per-channel delivery counts for the current billing period. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TrackerListResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.trackers.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, + *, + entity_name: str, + entity_type: CreateTrackersRequestEntityType, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[CreateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TrackerMutationResponse: + """ + Creates a standalone tracker watching one entity (name + type, resolved with the same entity resolution Search uses). Attach it to a monitor afterwards via POST /v1/monitors/{id}/trackers. Creating a duplicate (same entity name + type) returns 409 with the existingId. + + Parameters + ---------- + entity_name : str + The entity name to resolve and watch. Required on create. Creating a duplicate (same name + type) returns 409 with the existingId. + + entity_type : CreateTrackersRequestEntityType + Entity type of the tracked entity. Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[CreateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TrackerMutationResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.trackers.create( + entity_name="entityName", + entity_type="person", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + entity_name=entity_name, + entity_type=entity_type, + idempotency_key=idempotency_key, + display_name=display_name, + notify_email=notify_email, + notify_webhook=notify_webhook, + notify_slack=notify_slack, + webhook_url=webhook_url, + slack_channel_id=slack_channel_id, + slack_integration_id=slack_integration_id, + person_match_mode=person_match_mode, + filters=filters, + request_options=request_options, + ) + return _response.data + + async def delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> MessageResponse: + """ + Deletes the tracker. Cannot be undone. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MessageResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.trackers.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, idempotency_key=idempotency_key, request_options=request_options) + return _response.data + + async def update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[UpdateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + paused: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TrackerMutationResponse: + """ + Partial update: send only the fields to change. The tracked entity itself (entityName/entityType) is immutable; delete and recreate to watch a different entity. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[UpdateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + paused : typing.Optional[bool] + Pause or resume the tracker. Paused trackers stop producing alerts; there is no backfill for the paused window. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TrackerMutationResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.trackers.update( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + id, + idempotency_key=idempotency_key, + display_name=display_name, + notify_email=notify_email, + notify_webhook=notify_webhook, + notify_slack=notify_slack, + webhook_url=webhook_url, + slack_channel_id=slack_channel_id, + slack_integration_id=slack_integration_id, + person_match_mode=person_match_mode, + filters=filters, + paused=paused, + request_options=request_options, + ) + return _response.data + + @property + def alerts(self): + if self._alerts is None: + from .alerts.client import AsyncAlertsClient # noqa: E402 + + self._alerts = AsyncAlertsClient(client_wrapper=self._client_wrapper) + return self._alerts diff --git a/src/arcmira/trackers/raw_client.py b/src/arcmira/trackers/raw_client.py new file mode 100644 index 0000000..3afd11f --- /dev/null +++ b/src/arcmira/trackers/raw_client.py @@ -0,0 +1,1248 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.conflict_error import ConflictError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.message_response import MessageResponse +from ..types.tracker_list_response import TrackerListResponse +from ..types.tracker_mutation_response import TrackerMutationResponse +from .types.create_trackers_request_entity_type import CreateTrackersRequestEntityType +from .types.create_trackers_request_person_match_mode import CreateTrackersRequestPersonMatchMode +from .types.update_trackers_request_person_match_mode import UpdateTrackersRequestPersonMatchMode +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawTrackersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[TrackerListResponse]: + """ + All trackers for the account, newest first, with per-channel delivery counts for the current billing period. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TrackerListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/trackers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TrackerListResponse, + parse_obj_as( + type_=TrackerListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 create( + self, + *, + entity_name: str, + entity_type: CreateTrackersRequestEntityType, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[CreateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TrackerMutationResponse]: + """ + Creates a standalone tracker watching one entity (name + type, resolved with the same entity resolution Search uses). Attach it to a monitor afterwards via POST /v1/monitors/{id}/trackers. Creating a duplicate (same entity name + type) returns 409 with the existingId. + + Parameters + ---------- + entity_name : str + The entity name to resolve and watch. Required on create. Creating a duplicate (same name + type) returns 409 with the existingId. + + entity_type : CreateTrackersRequestEntityType + Entity type of the tracked entity. Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[CreateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TrackerMutationResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/trackers", + method="POST", + json={ + "entityName": entity_name, + "entityType": entity_type, + "displayName": display_name, + "notifyEmail": notify_email, + "notifyWebhook": notify_webhook, + "notifySlack": notify_slack, + "webhookUrl": webhook_url, + "slackChannelId": slack_channel_id, + "slackIntegrationId": slack_integration_id, + "personMatchMode": person_match_mode, + "filters": filters, + }, + 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( + TrackerMutationResponse, + parse_obj_as( + type_=TrackerMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MessageResponse]: + """ + Deletes the tracker. Cannot be undone. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MessageResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/trackers/{encode_path_param(id)}", + method="DELETE", + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MessageResponse, + parse_obj_as( + type_=MessageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[UpdateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + paused: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TrackerMutationResponse]: + """ + Partial update: send only the fields to change. The tracked entity itself (entityName/entityType) is immutable; delete and recreate to watch a different entity. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[UpdateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + paused : typing.Optional[bool] + Pause or resume the tracker. Paused trackers stop producing alerts; there is no backfill for the paused window. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TrackerMutationResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/trackers/{encode_path_param(id)}", + method="PATCH", + json={ + "displayName": display_name, + "notifyEmail": notify_email, + "notifyWebhook": notify_webhook, + "notifySlack": notify_slack, + "webhookUrl": webhook_url, + "slackChannelId": slack_channel_id, + "slackIntegrationId": slack_integration_id, + "personMatchMode": person_match_mode, + "filters": filters, + "paused": paused, + }, + 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( + TrackerMutationResponse, + parse_obj_as( + type_=TrackerMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawTrackersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TrackerListResponse]: + """ + All trackers for the account, newest first, with per-channel delivery counts for the current billing period. Single page, no pagination. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TrackerListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/trackers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TrackerListResponse, + parse_obj_as( + type_=TrackerListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 create( + self, + *, + entity_name: str, + entity_type: CreateTrackersRequestEntityType, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[CreateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TrackerMutationResponse]: + """ + Creates a standalone tracker watching one entity (name + type, resolved with the same entity resolution Search uses). Attach it to a monitor afterwards via POST /v1/monitors/{id}/trackers. Creating a duplicate (same entity name + type) returns 409 with the existingId. + + Parameters + ---------- + entity_name : str + The entity name to resolve and watch. Required on create. Creating a duplicate (same name + type) returns 409 with the existingId. + + entity_type : CreateTrackersRequestEntityType + Entity type of the tracked entity. Required on create. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[CreateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TrackerMutationResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/trackers", + method="POST", + json={ + "entityName": entity_name, + "entityType": entity_type, + "displayName": display_name, + "notifyEmail": notify_email, + "notifyWebhook": notify_webhook, + "notifySlack": notify_slack, + "webhookUrl": webhook_url, + "slackChannelId": slack_channel_id, + "slackIntegrationId": slack_integration_id, + "personMatchMode": person_match_mode, + "filters": filters, + }, + 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( + TrackerMutationResponse, + parse_obj_as( + type_=TrackerMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 delete( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MessageResponse]: + """ + Deletes the tracker. Cannot be undone. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MessageResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/trackers/{encode_path_param(id)}", + method="DELETE", + headers={ + "Idempotency-Key": str(idempotency_key) if idempotency_key is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MessageResponse, + parse_obj_as( + type_=MessageResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 update( + self, + id: str, + *, + idempotency_key: typing.Optional[str] = None, + display_name: typing.Optional[str] = OMIT, + notify_email: typing.Optional[bool] = OMIT, + notify_webhook: typing.Optional[bool] = OMIT, + notify_slack: typing.Optional[bool] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + slack_channel_id: typing.Optional[str] = OMIT, + slack_integration_id: typing.Optional[str] = OMIT, + person_match_mode: typing.Optional[UpdateTrackersRequestPersonMatchMode] = OMIT, + filters: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + paused: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TrackerMutationResponse]: + """ + Partial update: send only the fields to change. The tracked entity itself (entityName/entityType) is immutable; delete and recreate to watch a different entity. + + Parameters + ---------- + id : str + Tracker id, trk_ form. + + idempotency_key : typing.Optional[str] + Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + + display_name : typing.Optional[str] + Optional label shown in alerts and the dashboard. + + notify_email : typing.Optional[bool] + Per-tracker email delivery. Default true. + + notify_webhook : typing.Optional[bool] + Per-tracker webhook delivery override. Paid plans only. + + notify_slack : typing.Optional[bool] + Per-tracker Slack delivery override. Paid plans only. + + webhook_url : typing.Optional[str] + Per-tracker webhook destination override (http/https). + + slack_channel_id : typing.Optional[str] + Per-tracker Slack channel override. + + slack_integration_id : typing.Optional[str] + Per-tracker Slack integration override. + + person_match_mode : typing.Optional[UpdateTrackersRequestPersonMatchMode] + Person trackers only. Mentions (default) matches others talking about the person; appearances matches the person present as a speaker, host or guest; both accepts either. Non-person trackers reject this field. PATCH changes future and pending delivery eligibility, without backfill. + + filters : typing.Optional[typing.Dict[str, typing.Any]] + Stored filter object. personMatchMode is also accepted here for person trackers. Other filter keys are retained; do not assume they change matching. + + paused : typing.Optional[bool] + Pause or resume the tracker. Paused trackers stop producing alerts; there is no backfill for the paused window. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TrackerMutationResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/trackers/{encode_path_param(id)}", + method="PATCH", + json={ + "displayName": display_name, + "notifyEmail": notify_email, + "notifyWebhook": notify_webhook, + "notifySlack": notify_slack, + "webhookUrl": webhook_url, + "slackChannelId": slack_channel_id, + "slackIntegrationId": slack_integration_id, + "personMatchMode": person_match_mode, + "filters": filters, + "paused": paused, + }, + 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( + TrackerMutationResponse, + parse_obj_as( + type_=TrackerMutationResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/trackers/types/__init__.py b/src/arcmira/trackers/types/__init__.py new file mode 100644 index 0000000..a511ce7 --- /dev/null +++ b/src/arcmira/trackers/types/__init__.py @@ -0,0 +1,44 @@ +# 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 .create_trackers_request_entity_type import CreateTrackersRequestEntityType + from .create_trackers_request_person_match_mode import CreateTrackersRequestPersonMatchMode + from .update_trackers_request_person_match_mode import UpdateTrackersRequestPersonMatchMode +_dynamic_imports: typing.Dict[str, str] = { + "CreateTrackersRequestEntityType": ".create_trackers_request_entity_type", + "CreateTrackersRequestPersonMatchMode": ".create_trackers_request_person_match_mode", + "UpdateTrackersRequestPersonMatchMode": ".update_trackers_request_person_match_mode", +} + + +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__ = [ + "CreateTrackersRequestEntityType", + "CreateTrackersRequestPersonMatchMode", + "UpdateTrackersRequestPersonMatchMode", +] diff --git a/src/arcmira/trackers/types/create_trackers_request_entity_type.py b/src/arcmira/trackers/types/create_trackers_request_entity_type.py new file mode 100644 index 0000000..d4f3b03 --- /dev/null +++ b/src/arcmira/trackers/types/create_trackers_request_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreateTrackersRequestEntityType = typing.Union[ + typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any +] diff --git a/src/arcmira/trackers/types/create_trackers_request_person_match_mode.py b/src/arcmira/trackers/types/create_trackers_request_person_match_mode.py new file mode 100644 index 0000000..443baee --- /dev/null +++ b/src/arcmira/trackers/types/create_trackers_request_person_match_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreateTrackersRequestPersonMatchMode = typing.Union[typing.Literal["mentions", "appearances", "both"], typing.Any] diff --git a/src/arcmira/trackers/types/update_trackers_request_person_match_mode.py b/src/arcmira/trackers/types/update_trackers_request_person_match_mode.py new file mode 100644 index 0000000..436104c --- /dev/null +++ b/src/arcmira/trackers/types/update_trackers_request_person_match_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +UpdateTrackersRequestPersonMatchMode = typing.Union[typing.Literal["mentions", "appearances", "both"], typing.Any] diff --git a/src/arcmira/transcripts/__init__.py b/src/arcmira/transcripts/__init__.py new file mode 100644 index 0000000..969eebe --- /dev/null +++ b/src/arcmira/transcripts/__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 .types import ( + CaptionsTranscriptsRequestSrc, + GetTranscriptsRequestQuality, + GetTranscriptsRequestSrc, + ListRequestsTranscriptsRequestSrc, + SearchTranscriptsRequestSource, + SearchTranscriptsRequestSrc, + StatusTranscriptsRequestSrc, + ) + from . import edits, merges, speakers +_dynamic_imports: typing.Dict[str, str] = { + "CaptionsTranscriptsRequestSrc": ".types", + "GetTranscriptsRequestQuality": ".types", + "GetTranscriptsRequestSrc": ".types", + "ListRequestsTranscriptsRequestSrc": ".types", + "SearchTranscriptsRequestSource": ".types", + "SearchTranscriptsRequestSrc": ".types", + "StatusTranscriptsRequestSrc": ".types", + "edits": ".edits", + "merges": ".merges", + "speakers": ".speakers", +} + + +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__ = [ + "CaptionsTranscriptsRequestSrc", + "GetTranscriptsRequestQuality", + "GetTranscriptsRequestSrc", + "ListRequestsTranscriptsRequestSrc", + "SearchTranscriptsRequestSource", + "SearchTranscriptsRequestSrc", + "StatusTranscriptsRequestSrc", + "edits", + "merges", + "speakers", +] diff --git a/src/arcmira/transcripts/client.py b/src/arcmira/transcripts/client.py new file mode 100644 index 0000000..69bdc73 --- /dev/null +++ b/src/arcmira/transcripts/client.py @@ -0,0 +1,963 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pagination import AsyncPager, SyncPager +from ..core.request_options import RequestOptions +from ..types.transcript_purchase_quote import TranscriptPurchaseQuote +from ..types.transcript_result import TranscriptResult +from ..types.transcript_search_response import TranscriptSearchResponse +from ..types.transcription_list_response import TranscriptionListResponse +from ..types.transcription_list_response_requests_item import TranscriptionListResponseRequestsItem +from ..types.transcription_request import TranscriptionRequest +from ..types.transcription_submit_response import TranscriptionSubmitResponse +from ..types.video_captions_response import VideoCaptionsResponse +from .raw_client import AsyncRawTranscriptsClient, RawTranscriptsClient +from .types.captions_transcripts_request_src import CaptionsTranscriptsRequestSrc +from .types.get_transcripts_request_quality import GetTranscriptsRequestQuality +from .types.get_transcripts_request_src import GetTranscriptsRequestSrc +from .types.list_requests_transcripts_request_src import ListRequestsTranscriptsRequestSrc +from .types.search_transcripts_request_source import SearchTranscriptsRequestSource +from .types.search_transcripts_request_src import SearchTranscriptsRequestSrc +from .types.status_transcripts_request_src import StatusTranscriptsRequestSrc + +if typing.TYPE_CHECKING: + from .edits.client import AsyncEditsClient, EditsClient + from .merges.client import AsyncMergesClient, MergesClient + from .speakers.client import AsyncSpeakersClient, SpeakersClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class TranscriptsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTranscriptsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._edits: typing.Optional[EditsClient] = None + self._speakers: typing.Optional[SpeakersClient] = None + self._merges: typing.Optional[MergesClient] = None + + @property + def with_raw_response(self) -> RawTranscriptsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTranscriptsClient + """ + return self._raw_client + + def search( + self, + *, + q: str, + channel_ids: typing.Optional[str] = None, + channel: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + about: typing.Optional[str] = None, + by: typing.Optional[str] = None, + kind: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + source: typing.Optional[SearchTranscriptsRequestSource] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptSearchResponse: + """ + Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to mention, recommendation_sponsored or recommendation_organic passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per chunk returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + q : str + One topic or phrase. Do not concatenate unrelated names; make one call per topic. + + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + channel : typing.Optional[str] + Alias of channel_ids for code-mode clients; the union of both is the scope. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + about : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + by : typing.Optional[str] + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + kind : typing.Optional[str] + Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk. + + published_after : typing.Optional[str] + ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + source : typing.Optional[SearchTranscriptsRequestSource] + Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. + + limit : typing.Optional[int] + Chunks to return, 1 to 20. Default 5. + + src : typing.Optional[SearchTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptSearchResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.search( + q="q", + ) + """ + _response = self._raw_client.search( + q=q, + channel_ids=channel_ids, + channel=channel, + entity_ids=entity_ids, + about=about, + by=by, + kind=kind, + published_after=published_after, + published_before=published_before, + source=source, + limit=limit, + src=src, + request_options=request_options, + ) + return _response.data + + def get( + self, + video_id: str, + *, + quality: typing.Optional[GetTranscriptsRequestQuality] = None, + language: typing.Optional[str] = None, + timestamps: typing.Optional[bool] = None, + start: typing.Optional[float] = None, + end: typing.Optional[float] = None, + refresh: typing.Optional[bool] = None, + src: typing.Optional[GetTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptResult: + """ + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + quality : typing.Optional[GetTranscriptsRequestQuality] + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + + language : typing.Optional[str] + Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + + timestamps : typing.Optional[bool] + false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. + + start : typing.Optional[float] + Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge. + + end : typing.Optional[float] + Window end in seconds, greater than start and no greater than the video duration. Send start and end together. + + refresh : typing.Optional[bool] + Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. + + src : typing.Optional[GetTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptResult + The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.get( + video_id="video_id", + ) + """ + _response = self._raw_client.get( + video_id, + quality=quality, + language=language, + timestamps=timestamps, + start=start, + end=end, + refresh=refresh, + src=src, + request_options=request_options, + ) + return _response.data + + def quote( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TranscriptPurchaseQuote: + """ + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptPurchaseQuote + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.quote( + video_id="video_id", + ) + """ + _response = self._raw_client.quote(video_id, request_options=request_options) + return _response.data + + def captions( + self, + video_id: str, + *, + src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> VideoCaptionsResponse: + """ + Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + src : typing.Optional[CaptionsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VideoCaptionsResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.captions( + video_id="video_id", + ) + """ + _response = self._raw_client.captions(video_id, src=src, request_options=request_options) + return _response.data + + def list_requests( + self, + *, + video_id: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + """ + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + + Parameters + ---------- + video_id : typing.Optional[str] + Filter to your requests for one video. + + limit : typing.Optional[int] + Requests per page, from 1 to 100. Default 20. + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Keep the same filter, limit and credential. + + src : typing.Optional[ListRequestsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + response = client.transcripts.list_requests() + for item in response: + yield item + # alternatively, you can paginate page-by-page + for page in response.iter_pages(): + yield page + """ + return self._raw_client.list_requests( + video_id=video_id, limit=limit, cursor=cursor, src=src, request_options=request_options + ) + + def request( + self, + *, + idempotency_key: str, + max_rows: int, + max_on_demand_cents: typing.Optional[float] = OMIT, + video_id: typing.Optional[str] = OMIT, + url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptionSubmitResponse: + """ + Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + + Parameters + ---------- + idempotency_key : str + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + max_rows : int + Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. + + max_on_demand_cents : typing.Optional[float] + Maximum new monetary on-demand charge in cents. Omit to authorize none. + + video_id : typing.Optional[str] + YouTube video id (11 characters). Either videoId or url is required. + + url : typing.Optional[str] + A YouTube watch/short/live URL. Either videoId or url is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptionSubmitResponse + An existing in-flight or already-satisfied request was returned (existing: true) + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.request( + idempotency_key="Idempotency-Key", + max_rows=1, + ) + """ + _response = self._raw_client.request( + idempotency_key=idempotency_key, + max_rows=max_rows, + max_on_demand_cents=max_on_demand_cents, + video_id=video_id, + url=url, + request_options=request_options, + ) + return _response.data + + def status( + self, + id: str, + *, + src: typing.Optional[StatusTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptionRequest: + """ + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + + Parameters + ---------- + id : str + Transcription request id, the UUID POST /v1/transcriptions returned. + + src : typing.Optional[StatusTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptionRequest + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.status( + id="id", + ) + """ + _response = self._raw_client.status(id, src=src, request_options=request_options) + return _response.data + + @property + def edits(self): + if self._edits is None: + from .edits.client import EditsClient # noqa: E402 + + self._edits = EditsClient(client_wrapper=self._client_wrapper) + return self._edits + + @property + def speakers(self): + if self._speakers is None: + from .speakers.client import SpeakersClient # noqa: E402 + + self._speakers = SpeakersClient(client_wrapper=self._client_wrapper) + return self._speakers + + @property + def merges(self): + if self._merges is None: + from .merges.client import MergesClient # noqa: E402 + + self._merges = MergesClient(client_wrapper=self._client_wrapper) + return self._merges + + +class AsyncTranscriptsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTranscriptsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._edits: typing.Optional[AsyncEditsClient] = None + self._speakers: typing.Optional[AsyncSpeakersClient] = None + self._merges: typing.Optional[AsyncMergesClient] = None + + @property + def with_raw_response(self) -> AsyncRawTranscriptsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTranscriptsClient + """ + return self._raw_client + + async def search( + self, + *, + q: str, + channel_ids: typing.Optional[str] = None, + channel: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + about: typing.Optional[str] = None, + by: typing.Optional[str] = None, + kind: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + source: typing.Optional[SearchTranscriptsRequestSource] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptSearchResponse: + """ + Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to mention, recommendation_sponsored or recommendation_organic passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per chunk returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + q : str + One topic or phrase. Do not concatenate unrelated names; make one call per topic. + + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + channel : typing.Optional[str] + Alias of channel_ids for code-mode clients; the union of both is the scope. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + about : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + by : typing.Optional[str] + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + kind : typing.Optional[str] + Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk. + + published_after : typing.Optional[str] + ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + source : typing.Optional[SearchTranscriptsRequestSource] + Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. + + limit : typing.Optional[int] + Chunks to return, 1 to 20. Default 5. + + src : typing.Optional[SearchTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptSearchResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.search( + q="q", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.search( + q=q, + channel_ids=channel_ids, + channel=channel, + entity_ids=entity_ids, + about=about, + by=by, + kind=kind, + published_after=published_after, + published_before=published_before, + source=source, + limit=limit, + src=src, + request_options=request_options, + ) + return _response.data + + async def get( + self, + video_id: str, + *, + quality: typing.Optional[GetTranscriptsRequestQuality] = None, + language: typing.Optional[str] = None, + timestamps: typing.Optional[bool] = None, + start: typing.Optional[float] = None, + end: typing.Optional[float] = None, + refresh: typing.Optional[bool] = None, + src: typing.Optional[GetTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptResult: + """ + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + quality : typing.Optional[GetTranscriptsRequestQuality] + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + + language : typing.Optional[str] + Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + + timestamps : typing.Optional[bool] + false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. + + start : typing.Optional[float] + Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge. + + end : typing.Optional[float] + Window end in seconds, greater than start and no greater than the video duration. Send start and end together. + + refresh : typing.Optional[bool] + Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. + + src : typing.Optional[GetTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptResult + The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.get( + video_id="video_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + video_id, + quality=quality, + language=language, + timestamps=timestamps, + start=start, + end=end, + refresh=refresh, + src=src, + request_options=request_options, + ) + return _response.data + + async def quote( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TranscriptPurchaseQuote: + """ + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptPurchaseQuote + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.quote( + video_id="video_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.quote(video_id, request_options=request_options) + return _response.data + + async def captions( + self, + video_id: str, + *, + src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> VideoCaptionsResponse: + """ + Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + src : typing.Optional[CaptionsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VideoCaptionsResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.captions( + video_id="video_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.captions(video_id, src=src, request_options=request_options) + return _response.data + + async def list_requests( + self, + *, + video_id: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + """ + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + + Parameters + ---------- + video_id : typing.Optional[str] + Filter to your requests for one video. + + limit : typing.Optional[int] + Requests per page, from 1 to 100. Default 20. + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Keep the same filter, limit and credential. + + src : typing.Optional[ListRequestsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + response = await client.transcripts.list_requests() + async for item in response: + yield item + + # alternatively, you can paginate page-by-page + async for page in response.iter_pages(): + yield page + + + asyncio.run(main()) + """ + return await self._raw_client.list_requests( + video_id=video_id, limit=limit, cursor=cursor, src=src, request_options=request_options + ) + + async def request( + self, + *, + idempotency_key: str, + max_rows: int, + max_on_demand_cents: typing.Optional[float] = OMIT, + video_id: typing.Optional[str] = OMIT, + url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptionSubmitResponse: + """ + Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + + Parameters + ---------- + idempotency_key : str + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + max_rows : int + Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. + + max_on_demand_cents : typing.Optional[float] + Maximum new monetary on-demand charge in cents. Omit to authorize none. + + video_id : typing.Optional[str] + YouTube video id (11 characters). Either videoId or url is required. + + url : typing.Optional[str] + A YouTube watch/short/live URL. Either videoId or url is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptionSubmitResponse + An existing in-flight or already-satisfied request was returned (existing: true) + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.request( + idempotency_key="Idempotency-Key", + max_rows=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.request( + idempotency_key=idempotency_key, + max_rows=max_rows, + max_on_demand_cents=max_on_demand_cents, + video_id=video_id, + url=url, + request_options=request_options, + ) + return _response.data + + async def status( + self, + id: str, + *, + src: typing.Optional[StatusTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptionRequest: + """ + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + + Parameters + ---------- + id : str + Transcription request id, the UUID POST /v1/transcriptions returned. + + src : typing.Optional[StatusTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptionRequest + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.status( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.status(id, src=src, request_options=request_options) + return _response.data + + @property + def edits(self): + if self._edits is None: + from .edits.client import AsyncEditsClient # noqa: E402 + + self._edits = AsyncEditsClient(client_wrapper=self._client_wrapper) + return self._edits + + @property + def speakers(self): + if self._speakers is None: + from .speakers.client import AsyncSpeakersClient # noqa: E402 + + self._speakers = AsyncSpeakersClient(client_wrapper=self._client_wrapper) + return self._speakers + + @property + def merges(self): + if self._merges is None: + from .merges.client import AsyncMergesClient # noqa: E402 + + self._merges = AsyncMergesClient(client_wrapper=self._client_wrapper) + return self._merges diff --git a/src/arcmira/transcripts/edits/__init__.py b/src/arcmira/transcripts/edits/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/transcripts/edits/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/transcripts/edits/client.py b/src/arcmira/transcripts/edits/client.py new file mode 100644 index 0000000..9719d0d --- /dev/null +++ b/src/arcmira/transcripts/edits/client.py @@ -0,0 +1,260 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.transcript_edit_submitted_response import TranscriptEditSubmittedResponse +from ...types.withdrawn_response import WithdrawnResponse +from .raw_client import AsyncRawEditsClient, RawEditsClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class EditsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawEditsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawEditsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawEditsClient + """ + return self._raw_client + + def submit( + self, + video_id: str, + *, + segment_index: int, + original_text: str, + corrected_text: str, + idempotency_key: typing.Optional[str] = None, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptEditSubmittedResponse: + """ + Purpose-built wrapper for the line_edit kind. The edit is pending review: visible to you immediately (returned in the transcript GET `edits[]`), applied for everyone once approved. Free (0 rows), attributed to your API key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + segment_index : int + + original_text : str + The current segment text you are correcting (guards against applying to a changed segment). + + corrected_text : str + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + revision : typing.Optional[str] + The revision from the Premium transcript read. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptEditSubmittedResponse + Edit accepted, pending review + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.edits.submit( + video_id="video_id", + segment_index=1, + original_text="originalText", + corrected_text="correctedText", + ) + """ + _response = self._raw_client.submit( + video_id, + segment_index=segment_index, + original_text=original_text, + corrected_text=corrected_text, + idempotency_key=idempotency_key, + revision=revision, + request_options=request_options, + ) + return _response.data + + def withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.edits.withdraw( + video_id="video_id", + id="id", + ) + """ + _response = self._raw_client.withdraw(video_id, id, request_options=request_options) + return _response.data + + +class AsyncEditsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawEditsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawEditsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawEditsClient + """ + return self._raw_client + + async def submit( + self, + video_id: str, + *, + segment_index: int, + original_text: str, + corrected_text: str, + idempotency_key: typing.Optional[str] = None, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> TranscriptEditSubmittedResponse: + """ + Purpose-built wrapper for the line_edit kind. The edit is pending review: visible to you immediately (returned in the transcript GET `edits[]`), applied for everyone once approved. Free (0 rows), attributed to your API key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + segment_index : int + + original_text : str + The current segment text you are correcting (guards against applying to a changed segment). + + corrected_text : str + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + revision : typing.Optional[str] + The revision from the Premium transcript read. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TranscriptEditSubmittedResponse + Edit accepted, pending review + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.edits.submit( + video_id="video_id", + segment_index=1, + original_text="originalText", + corrected_text="correctedText", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.submit( + video_id, + segment_index=segment_index, + original_text=original_text, + corrected_text=corrected_text, + idempotency_key=idempotency_key, + revision=revision, + request_options=request_options, + ) + return _response.data + + async def withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.edits.withdraw( + video_id="video_id", + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.withdraw(video_id, id, request_options=request_options) + return _response.data diff --git a/src/arcmira/transcripts/edits/raw_client.py b/src/arcmira/transcripts/edits/raw_client.py new file mode 100644 index 0000000..2a02bb1 --- /dev/null +++ b/src/arcmira/transcripts/edits/raw_client.py @@ -0,0 +1,560 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.conflict_error import ConflictError +from ...errors.forbidden_error import ForbiddenError +from ...errors.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.transcript_edit_submitted_response import TranscriptEditSubmittedResponse +from ...types.withdrawn_response import WithdrawnResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawEditsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def submit( + self, + video_id: str, + *, + segment_index: int, + original_text: str, + corrected_text: str, + idempotency_key: typing.Optional[str] = None, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TranscriptEditSubmittedResponse]: + """ + Purpose-built wrapper for the line_edit kind. The edit is pending review: visible to you immediately (returned in the transcript GET `edits[]`), applied for everyone once approved. Free (0 rows), attributed to your API key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + segment_index : int + + original_text : str + The current segment text you are correcting (guards against applying to a changed segment). + + corrected_text : str + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + revision : typing.Optional[str] + The revision from the Premium transcript read. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TranscriptEditSubmittedResponse] + Edit accepted, pending review + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/edits", + method="POST", + json={ + "segmentIndex": segment_index, + "originalText": original_text, + "correctedText": corrected_text, + "revision": revision, + }, + 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( + TranscriptEditSubmittedResponse, + parse_obj_as( + type_=TranscriptEditSubmittedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/edits/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawEditsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def submit( + self, + video_id: str, + *, + segment_index: int, + original_text: str, + corrected_text: str, + idempotency_key: typing.Optional[str] = None, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TranscriptEditSubmittedResponse]: + """ + Purpose-built wrapper for the line_edit kind. The edit is pending review: visible to you immediately (returned in the transcript GET `edits[]`), applied for everyone once approved. Free (0 rows), attributed to your API key. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + segment_index : int + + original_text : str + The current segment text you are correcting (guards against applying to a changed segment). + + corrected_text : str + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + revision : typing.Optional[str] + The revision from the Premium transcript read. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TranscriptEditSubmittedResponse] + Edit accepted, pending review + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/edits", + method="POST", + json={ + "segmentIndex": segment_index, + "originalText": original_text, + "correctedText": corrected_text, + "revision": revision, + }, + 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( + TranscriptEditSubmittedResponse, + parse_obj_as( + type_=TranscriptEditSubmittedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/edits/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/transcripts/merges/__init__.py b/src/arcmira/transcripts/merges/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/transcripts/merges/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/transcripts/merges/client.py b/src/arcmira/transcripts/merges/client.py new file mode 100644 index 0000000..394a650 --- /dev/null +++ b/src/arcmira/transcripts/merges/client.py @@ -0,0 +1,329 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.video_merge_list_response import VideoMergeListResponse +from ...types.video_merge_submitted_response import VideoMergeSubmittedResponse +from ...types.withdrawn_response import WithdrawnResponse +from .raw_client import AsyncRawMergesClient, RawMergesClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class MergesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMergesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMergesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMergesClient + """ + return self._raw_client + + def list(self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> VideoMergeListResponse: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VideoMergeListResponse + Success + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.merges.list( + video_id="video_id", + ) + """ + _response = self._raw_client.list(video_id, request_options=request_options) + return _response.data + + def submit( + self, + video_id: str, + *, + source_name: str, + target_entity_id: int, + idempotency_key: typing.Optional[str] = None, + replace_with: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> VideoMergeSubmittedResponse: + """ + Asserts that a name in this video refers to a specific entity, for misattributed name mentions in one video (e.g. a first-name-only mention resolved to the wrong entity). Optionally respells the transcript text via `replaceWith`. Pending review; applied optimistically for you. Mentions of the same name in other videos are untouched. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + source_name : str + The name as it appears in this video (e.g. a first-name-only mention). + + target_entity_id : int + The canonical entity these mentions actually refer to. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + replace_with : typing.Optional[str] + Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). + + revision : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VideoMergeSubmittedResponse + Merge accepted, pending review + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.merges.submit( + video_id="video_id", + source_name="sourceName", + target_entity_id=1, + ) + """ + _response = self._raw_client.submit( + video_id, + source_name=source_name, + target_entity_id=target_entity_id, + idempotency_key=idempotency_key, + replace_with=replace_with, + revision=revision, + request_options=request_options, + ) + return _response.data + + def withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.merges.withdraw( + video_id="video_id", + id="id", + ) + """ + _response = self._raw_client.withdraw(video_id, id, request_options=request_options) + return _response.data + + +class AsyncMergesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMergesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMergesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMergesClient + """ + return self._raw_client + + async def list( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> VideoMergeListResponse: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VideoMergeListResponse + Success + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.merges.list( + video_id="video_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(video_id, request_options=request_options) + return _response.data + + async def submit( + self, + video_id: str, + *, + source_name: str, + target_entity_id: int, + idempotency_key: typing.Optional[str] = None, + replace_with: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> VideoMergeSubmittedResponse: + """ + Asserts that a name in this video refers to a specific entity, for misattributed name mentions in one video (e.g. a first-name-only mention resolved to the wrong entity). Optionally respells the transcript text via `replaceWith`. Pending review; applied optimistically for you. Mentions of the same name in other videos are untouched. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + source_name : str + The name as it appears in this video (e.g. a first-name-only mention). + + target_entity_id : int + The canonical entity these mentions actually refer to. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + replace_with : typing.Optional[str] + Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). + + revision : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VideoMergeSubmittedResponse + Merge accepted, pending review + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.merges.submit( + video_id="video_id", + source_name="sourceName", + target_entity_id=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.submit( + video_id, + source_name=source_name, + target_entity_id=target_entity_id, + idempotency_key=idempotency_key, + replace_with=replace_with, + revision=revision, + request_options=request_options, + ) + return _response.data + + async def withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.merges.withdraw( + video_id="video_id", + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.withdraw(video_id, id, request_options=request_options) + return _response.data diff --git a/src/arcmira/transcripts/merges/raw_client.py b/src/arcmira/transcripts/merges/raw_client.py new file mode 100644 index 0000000..e8f1fe3 --- /dev/null +++ b/src/arcmira/transcripts/merges/raw_client.py @@ -0,0 +1,777 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.conflict_error import ConflictError +from ...errors.forbidden_error import ForbiddenError +from ...errors.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.video_merge_list_response import VideoMergeListResponse +from ...types.video_merge_submitted_response import VideoMergeSubmittedResponse +from ...types.withdrawn_response import WithdrawnResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawMergesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[VideoMergeListResponse]: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[VideoMergeListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/merges", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VideoMergeListResponse, + parse_obj_as( + type_=VideoMergeListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 submit( + self, + video_id: str, + *, + source_name: str, + target_entity_id: int, + idempotency_key: typing.Optional[str] = None, + replace_with: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[VideoMergeSubmittedResponse]: + """ + Asserts that a name in this video refers to a specific entity, for misattributed name mentions in one video (e.g. a first-name-only mention resolved to the wrong entity). Optionally respells the transcript text via `replaceWith`. Pending review; applied optimistically for you. Mentions of the same name in other videos are untouched. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + source_name : str + The name as it appears in this video (e.g. a first-name-only mention). + + target_entity_id : int + The canonical entity these mentions actually refer to. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + replace_with : typing.Optional[str] + Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). + + revision : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[VideoMergeSubmittedResponse] + Merge accepted, pending review + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/merges", + method="POST", + json={ + "sourceName": source_name, + "targetEntityId": target_entity_id, + "replaceWith": replace_with, + "revision": revision, + }, + 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( + VideoMergeSubmittedResponse, + parse_obj_as( + type_=VideoMergeSubmittedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/merges/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawMergesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[VideoMergeListResponse]: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[VideoMergeListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/merges", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VideoMergeListResponse, + parse_obj_as( + type_=VideoMergeListResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 submit( + self, + video_id: str, + *, + source_name: str, + target_entity_id: int, + idempotency_key: typing.Optional[str] = None, + replace_with: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[VideoMergeSubmittedResponse]: + """ + Asserts that a name in this video refers to a specific entity, for misattributed name mentions in one video (e.g. a first-name-only mention resolved to the wrong entity). Optionally respells the transcript text via `replaceWith`. Pending review; applied optimistically for you. Mentions of the same name in other videos are untouched. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + source_name : str + The name as it appears in this video (e.g. a first-name-only mention). + + target_entity_id : int + The canonical entity these mentions actually refer to. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + replace_with : typing.Optional[str] + Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). + + revision : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[VideoMergeSubmittedResponse] + Merge accepted, pending review + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/merges", + method="POST", + json={ + "sourceName": source_name, + "targetEntityId": target_entity_id, + "replaceWith": replace_with, + "revision": revision, + }, + 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( + VideoMergeSubmittedResponse, + parse_obj_as( + type_=VideoMergeSubmittedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WithdrawnResponse]: + """ + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/merges/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/transcripts/raw_client.py b/src/arcmira/transcripts/raw_client.py new file mode 100644 index 0000000..41964e2 --- /dev/null +++ b/src/arcmira/transcripts/raw_client.py @@ -0,0 +1,2122 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +from ..core.pagination import AsyncPager, SyncPager +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.conflict_error import ConflictError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.not_found_error import NotFoundError +from ..errors.payment_required_error import PaymentRequiredError +from ..errors.service_unavailable_error import ServiceUnavailableError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.error import Error +from ..types.transcript_purchase_quote import TranscriptPurchaseQuote +from ..types.transcript_result import TranscriptResult +from ..types.transcript_search_response import TranscriptSearchResponse +from ..types.transcription_list_response import TranscriptionListResponse +from ..types.transcription_list_response_requests_item import TranscriptionListResponseRequestsItem +from ..types.transcription_request import TranscriptionRequest +from ..types.transcription_submit_response import TranscriptionSubmitResponse +from ..types.video_captions_response import VideoCaptionsResponse +from .types.captions_transcripts_request_src import CaptionsTranscriptsRequestSrc +from .types.get_transcripts_request_quality import GetTranscriptsRequestQuality +from .types.get_transcripts_request_src import GetTranscriptsRequestSrc +from .types.list_requests_transcripts_request_src import ListRequestsTranscriptsRequestSrc +from .types.search_transcripts_request_source import SearchTranscriptsRequestSource +from .types.search_transcripts_request_src import SearchTranscriptsRequestSrc +from .types.status_transcripts_request_src import StatusTranscriptsRequestSrc +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawTranscriptsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def search( + self, + *, + q: str, + channel_ids: typing.Optional[str] = None, + channel: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + about: typing.Optional[str] = None, + by: typing.Optional[str] = None, + kind: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + source: typing.Optional[SearchTranscriptsRequestSource] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TranscriptSearchResponse]: + """ + Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to mention, recommendation_sponsored or recommendation_organic passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per chunk returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + q : str + One topic or phrase. Do not concatenate unrelated names; make one call per topic. + + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + channel : typing.Optional[str] + Alias of channel_ids for code-mode clients; the union of both is the scope. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + about : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + by : typing.Optional[str] + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + kind : typing.Optional[str] + Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk. + + published_after : typing.Optional[str] + ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + source : typing.Optional[SearchTranscriptsRequestSource] + Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. + + limit : typing.Optional[int] + Chunks to return, 1 to 20. Default 5. + + src : typing.Optional[SearchTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TranscriptSearchResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/transcripts/search", + method="GET", + params={ + "q": q, + "channel_ids": channel_ids, + "channel": channel, + "entity_ids": entity_ids, + "about": about, + "by": by, + "kind": kind, + "published_after": published_after, + "published_before": published_before, + "source": source, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptSearchResponse, + parse_obj_as( + type_=TranscriptSearchResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, + video_id: str, + *, + quality: typing.Optional[GetTranscriptsRequestQuality] = None, + language: typing.Optional[str] = None, + timestamps: typing.Optional[bool] = None, + start: typing.Optional[float] = None, + end: typing.Optional[float] = None, + refresh: typing.Optional[bool] = None, + src: typing.Optional[GetTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TranscriptResult]: + """ + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + quality : typing.Optional[GetTranscriptsRequestQuality] + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + + language : typing.Optional[str] + Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + + timestamps : typing.Optional[bool] + false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. + + start : typing.Optional[float] + Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge. + + end : typing.Optional[float] + Window end in seconds, greater than start and no greater than the video duration. Send start and end together. + + refresh : typing.Optional[bool] + Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. + + src : typing.Optional[GetTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TranscriptResult] + The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}", + method="GET", + params={ + "quality": quality, + "language": language, + "timestamps": timestamps, + "start": start, + "end": end, + "refresh": refresh, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptResult, + parse_obj_as( + type_=TranscriptResult, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 quote( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TranscriptPurchaseQuote]: + """ + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TranscriptPurchaseQuote] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/quote", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptPurchaseQuote, + parse_obj_as( + type_=TranscriptPurchaseQuote, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 captions( + self, + video_id: str, + *, + src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[VideoCaptionsResponse]: + """ + Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + src : typing.Optional[CaptionsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[VideoCaptionsResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/videos/{encode_path_param(video_id)}/captions", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VideoCaptionsResponse, + parse_obj_as( + type_=VideoCaptionsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 list_requests( + self, + *, + video_id: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + """ + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + + Parameters + ---------- + video_id : typing.Optional[str] + Filter to your requests for one video. + + limit : typing.Optional[int] + Requests per page, from 1 to 100. Default 20. + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Keep the same filter, limit and credential. + + src : typing.Optional[ListRequestsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + Success + """ + _response = self._client_wrapper.httpx_client.request( + "v1/transcriptions", + method="GET", + params={ + "video_id": video_id, + "limit": limit, + "cursor": cursor, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + TranscriptionListResponse, + parse_obj_as( + type_=TranscriptionListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.requests + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + _get_next = lambda: self.list_requests( + video_id=video_id, + limit=limit, + cursor=_parsed_next, + src=src, + request_options=request_options, + ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 request( + self, + *, + idempotency_key: str, + max_rows: int, + max_on_demand_cents: typing.Optional[float] = OMIT, + video_id: typing.Optional[str] = OMIT, + url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TranscriptionSubmitResponse]: + """ + Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + + Parameters + ---------- + idempotency_key : str + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + max_rows : int + Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. + + max_on_demand_cents : typing.Optional[float] + Maximum new monetary on-demand charge in cents. Omit to authorize none. + + video_id : typing.Optional[str] + YouTube video id (11 characters). Either videoId or url is required. + + url : typing.Optional[str] + A YouTube watch/short/live URL. Either videoId or url is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TranscriptionSubmitResponse] + An existing in-flight or already-satisfied request was returned (existing: true) + """ + _response = self._client_wrapper.httpx_client.request( + "v1/transcriptions", + method="POST", + json={ + "max_on_demand_cents": max_on_demand_cents, + "max_rows": max_rows, + "videoId": video_id, + "url": url, + }, + 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( + TranscriptionSubmitResponse, + parse_obj_as( + type_=TranscriptionSubmitResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 status( + self, + id: str, + *, + src: typing.Optional[StatusTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TranscriptionRequest]: + """ + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + + Parameters + ---------- + id : str + Transcription request id, the UUID POST /v1/transcriptions returned. + + src : typing.Optional[StatusTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TranscriptionRequest] + Success + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcriptions/{encode_path_param(id)}", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptionRequest, + parse_obj_as( + type_=TranscriptionRequest, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawTranscriptsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def search( + self, + *, + q: str, + channel_ids: typing.Optional[str] = None, + channel: typing.Optional[str] = None, + entity_ids: typing.Optional[str] = None, + about: typing.Optional[str] = None, + by: typing.Optional[str] = None, + kind: typing.Optional[str] = None, + published_after: typing.Optional[str] = None, + published_before: typing.Optional[str] = None, + source: typing.Optional[SearchTranscriptsRequestSource] = None, + limit: typing.Optional[int] = None, + src: typing.Optional[SearchTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TranscriptSearchResponse]: + """ + Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to mention, recommendation_sponsored or recommendation_organic passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. A published_after narrower than the plan's freshness gate is refused with freshness_requires_paid rather than widened. Bills one row per chunk returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + + Parameters + ---------- + q : str + One topic or phrase. Do not concatenate unrelated names; make one call per topic. + + channel_ids : typing.Optional[str] + Comma-separated YouTube channel ids (UC...), at most 8. Pass every show in scope unless drilling into one. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + channel : typing.Optional[str] + Alias of channel_ids for code-mode clients; the union of both is the scope. + + entity_ids : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. A person id filters to that person's appearances; a channel id widens channel_ids. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + about : typing.Optional[str] + Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + by : typing.Optional[str] + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + + kind : typing.Optional[str] + Comma-separated passage kinds: mention, recommendation_sponsored, recommendation_organic. Combine with about to read what was said about a brand in ad reads or in organic talk. + + published_after : typing.Optional[str] + ISO date. Only media published on or after this day. A window narrower than your plan's freshness gate is refused with freshness_requires_paid rather than widened. + + published_before : typing.Optional[str] + ISO date. Only media published before this day. + + source : typing.Optional[SearchTranscriptsRequestSource] + Restrict to one transcript source class. arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. + + limit : typing.Optional[int] + Chunks to return, 1 to 20. Default 5. + + src : typing.Optional[SearchTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TranscriptSearchResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/transcripts/search", + method="GET", + params={ + "q": q, + "channel_ids": channel_ids, + "channel": channel, + "entity_ids": entity_ids, + "about": about, + "by": by, + "kind": kind, + "published_after": published_after, + "published_before": published_before, + "source": source, + "limit": limit, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptSearchResponse, + parse_obj_as( + type_=TranscriptSearchResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 get( + self, + video_id: str, + *, + quality: typing.Optional[GetTranscriptsRequestQuality] = None, + language: typing.Optional[str] = None, + timestamps: typing.Optional[bool] = None, + start: typing.Optional[float] = None, + end: typing.Optional[float] = None, + refresh: typing.Optional[bool] = None, + src: typing.Optional[GetTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TranscriptResult]: + """ + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + quality : typing.Optional[GetTranscriptsRequestQuality] + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + + language : typing.Optional[str] + Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + + timestamps : typing.Optional[bool] + false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. + + start : typing.Optional[float] + Window start in seconds from the beginning of the video. Send start and end together. On captions the window bills only its own started 15-minute blocks; on Premium it trims an already purchased transcript; this GET does not charge. + + end : typing.Optional[float] + Window end in seconds, greater than start and no greater than the video duration. Send start and end together. + + refresh : typing.Optional[bool] + Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. + + src : typing.Optional[GetTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TranscriptResult] + The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}", + method="GET", + params={ + "quality": quality, + "language": language, + "timestamps": timestamps, + "start": start, + "end": end, + "refresh": refresh, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptResult, + parse_obj_as( + type_=TranscriptResult, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 quote( + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TranscriptPurchaseQuote]: + """ + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TranscriptPurchaseQuote] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/quote", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptPurchaseQuote, + parse_obj_as( + type_=TranscriptPurchaseQuote, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 captions( + self, + video_id: str, + *, + src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[VideoCaptionsResponse]: + """ + Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + src : typing.Optional[CaptionsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[VideoCaptionsResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/videos/{encode_path_param(video_id)}/captions", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VideoCaptionsResponse, + parse_obj_as( + type_=VideoCaptionsResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 list_requests( + self, + *, + video_id: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + """ + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + + Parameters + ---------- + video_id : typing.Optional[str] + Filter to your requests for one video. + + limit : typing.Optional[int] + Requests per page, from 1 to 100. Default 20. + + cursor : typing.Optional[str] + Signed continuation from next_cursor. Keep the same filter, limit and credential. + + src : typing.Optional[ListRequestsTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/transcriptions", + method="GET", + params={ + "video_id": video_id, + "limit": limit, + "cursor": cursor, + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast( + TranscriptionListResponse, + parse_obj_as( + type_=TranscriptionListResponse, # type: ignore + object_=_response.json(), + ), + ) + _items = _parsed_response.requests + _parsed_next = _parsed_response.next_cursor + _has_next = _parsed_next is not None and _parsed_next != "" + + async def _get_next(): + return await self.list_requests( + video_id=video_id, + limit=limit, + cursor=_parsed_next, + src=src, + request_options=request_options, + ) + + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 request( + self, + *, + idempotency_key: str, + max_rows: int, + max_on_demand_cents: typing.Optional[float] = OMIT, + video_id: typing.Optional[str] = OMIT, + url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TranscriptionSubmitResponse]: + """ + Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + + Parameters + ---------- + idempotency_key : str + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + max_rows : int + Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. + + max_on_demand_cents : typing.Optional[float] + Maximum new monetary on-demand charge in cents. Omit to authorize none. + + video_id : typing.Optional[str] + YouTube video id (11 characters). Either videoId or url is required. + + url : typing.Optional[str] + A YouTube watch/short/live URL. Either videoId or url is required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TranscriptionSubmitResponse] + An existing in-flight or already-satisfied request was returned (existing: true) + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/transcriptions", + method="POST", + json={ + "max_on_demand_cents": max_on_demand_cents, + "max_rows": max_rows, + "videoId": video_id, + "url": url, + }, + 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( + TranscriptionSubmitResponse, + parse_obj_as( + type_=TranscriptionSubmitResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 402: + raise PaymentRequiredError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 status( + self, + id: str, + *, + src: typing.Optional[StatusTranscriptsRequestSrc] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TranscriptionRequest]: + """ + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + + Parameters + ---------- + id : str + Transcription request id, the UUID POST /v1/transcriptions returned. + + src : typing.Optional[StatusTranscriptsRequestSrc] + The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TranscriptionRequest] + Success + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcriptions/{encode_path_param(id)}", + method="GET", + params={ + "src": src, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TranscriptionRequest, + parse_obj_as( + type_=TranscriptionRequest, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/transcripts/speakers/__init__.py b/src/arcmira/transcripts/speakers/__init__.py new file mode 100644 index 0000000..dadae70 --- /dev/null +++ b/src/arcmira/transcripts/speakers/__init__.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file diff --git a/src/arcmira/transcripts/speakers/client.py b/src/arcmira/transcripts/speakers/client.py new file mode 100644 index 0000000..06c6939 --- /dev/null +++ b/src/arcmira/transcripts/speakers/client.py @@ -0,0 +1,264 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.speaker_identification_submitted_response import SpeakerIdentificationSubmittedResponse +from ...types.withdrawn_response import WithdrawnResponse +from .raw_client import AsyncRawSpeakersClient, RawSpeakersClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SpeakersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSpeakersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSpeakersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSpeakersClient + """ + return self._raw_client + + def identify( + self, + video_id: str, + *, + speaker_id: int, + idempotency_key: typing.Optional[str] = None, + entity_id: typing.Optional[int] = OMIT, + name: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SpeakerIdentificationSubmittedResponse: + """ + Links a diarization speaker id to a person entity (or proposes a new person via `name`). Creates a community-attributed appearance immediately. It shows on the person page right away, flagged pending review; reviewers can revert it. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + speaker_id : int + A speakers[].id from the Premium transcript read that revision names. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + entity_id : typing.Optional[int] + Existing person entity id. Either entityId or name is required. + + name : typing.Optional[str] + Propose a person not in the index yet. Reuses an existing same-name person or creates a provisional one. Either entityId or name is required. + + revision : typing.Optional[str] + The revision of the transcript read speakerId came from. Required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SpeakerIdentificationSubmittedResponse + Identification accepted, pending review + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.speakers.identify( + video_id="video_id", + speaker_id=1, + ) + """ + _response = self._raw_client.identify( + video_id, + speaker_id=speaker_id, + idempotency_key=idempotency_key, + entity_id=entity_id, + name=name, + revision=revision, + request_options=request_options, + ) + return _response.data + + def withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Withdrawing also removes the community-attributed appearance the identification created. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + from arcmira import Arcmira + + client = Arcmira( + api_key="YOUR_API_KEY", + ) + client.transcripts.speakers.withdraw( + video_id="video_id", + id="id", + ) + """ + _response = self._raw_client.withdraw(video_id, id, request_options=request_options) + return _response.data + + +class AsyncSpeakersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSpeakersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSpeakersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSpeakersClient + """ + return self._raw_client + + async def identify( + self, + video_id: str, + *, + speaker_id: int, + idempotency_key: typing.Optional[str] = None, + entity_id: typing.Optional[int] = OMIT, + name: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SpeakerIdentificationSubmittedResponse: + """ + Links a diarization speaker id to a person entity (or proposes a new person via `name`). Creates a community-attributed appearance immediately. It shows on the person page right away, flagged pending review; reviewers can revert it. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + speaker_id : int + A speakers[].id from the Premium transcript read that revision names. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + entity_id : typing.Optional[int] + Existing person entity id. Either entityId or name is required. + + name : typing.Optional[str] + Propose a person not in the index yet. Reuses an existing same-name person or creates a provisional one. Either entityId or name is required. + + revision : typing.Optional[str] + The revision of the transcript read speakerId came from. Required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SpeakerIdentificationSubmittedResponse + Identification accepted, pending review + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.speakers.identify( + video_id="video_id", + speaker_id=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.identify( + video_id, + speaker_id=speaker_id, + idempotency_key=idempotency_key, + entity_id=entity_id, + name=name, + revision=revision, + request_options=request_options, + ) + return _response.data + + async def withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> WithdrawnResponse: + """ + Withdrawing also removes the community-attributed appearance the identification created. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WithdrawnResponse + Withdrawn + + Examples + -------- + import asyncio + + from arcmira import AsyncArcmira + + client = AsyncArcmira( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.transcripts.speakers.withdraw( + video_id="video_id", + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.withdraw(video_id, id, request_options=request_options) + return _response.data diff --git a/src/arcmira/transcripts/speakers/raw_client.py b/src/arcmira/transcripts/speakers/raw_client.py new file mode 100644 index 0000000..2a9b495 --- /dev/null +++ b/src/arcmira/transcripts/speakers/raw_client.py @@ -0,0 +1,568 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +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 encode_path_param +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.conflict_error import ConflictError +from ...errors.forbidden_error import ForbiddenError +from ...errors.internal_server_error import InternalServerError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.error import Error +from ...types.speaker_identification_submitted_response import SpeakerIdentificationSubmittedResponse +from ...types.withdrawn_response import WithdrawnResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSpeakersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def identify( + self, + video_id: str, + *, + speaker_id: int, + idempotency_key: typing.Optional[str] = None, + entity_id: typing.Optional[int] = OMIT, + name: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SpeakerIdentificationSubmittedResponse]: + """ + Links a diarization speaker id to a person entity (or proposes a new person via `name`). Creates a community-attributed appearance immediately. It shows on the person page right away, flagged pending review; reviewers can revert it. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + speaker_id : int + A speakers[].id from the Premium transcript read that revision names. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + entity_id : typing.Optional[int] + Existing person entity id. Either entityId or name is required. + + name : typing.Optional[str] + Propose a person not in the index yet. Reuses an existing same-name person or creates a provisional one. Either entityId or name is required. + + revision : typing.Optional[str] + The revision of the transcript read speakerId came from. Required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SpeakerIdentificationSubmittedResponse] + Identification accepted, pending review + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/speakers", + method="POST", + json={ + "speakerId": speaker_id, + "entityId": entity_id, + "name": name, + "revision": revision, + }, + 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( + SpeakerIdentificationSubmittedResponse, + parse_obj_as( + type_=SpeakerIdentificationSubmittedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WithdrawnResponse]: + """ + Withdrawing also removes the community-attributed appearance the identification created. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/speakers/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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) + + +class AsyncRawSpeakersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def identify( + self, + video_id: str, + *, + speaker_id: int, + idempotency_key: typing.Optional[str] = None, + entity_id: typing.Optional[int] = OMIT, + name: typing.Optional[str] = OMIT, + revision: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SpeakerIdentificationSubmittedResponse]: + """ + Links a diarization speaker id to a person entity (or proposes a new person via `name`). Creates a community-attributed appearance immediately. It shows on the person page right away, flagged pending review; reviewers can revert it. Free (0 rows). + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + speaker_id : int + A speakers[].id from the Premium transcript read that revision names. + + idempotency_key : typing.Optional[str] + Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + + entity_id : typing.Optional[int] + Existing person entity id. Either entityId or name is required. + + name : typing.Optional[str] + Propose a person not in the index yet. Reuses an existing same-name person or creates a provisional one. Either entityId or name is required. + + revision : typing.Optional[str] + The revision of the transcript read speakerId came from. Required. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SpeakerIdentificationSubmittedResponse] + Identification accepted, pending review + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/speakers", + method="POST", + json={ + "speakerId": speaker_id, + "entityId": entity_id, + "name": name, + "revision": revision, + }, + 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( + SpeakerIdentificationSubmittedResponse, + parse_obj_as( + type_=SpeakerIdentificationSubmittedResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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 withdraw( + self, video_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WithdrawnResponse]: + """ + Withdrawing also removes the community-attributed appearance the identification created. + + Parameters + ---------- + video_id : str + YouTube video id, 11 characters. + + id : str + The pending row id, as result.id of the response that accepted it. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WithdrawnResponse] + Withdrawn + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/transcripts/{encode_path_param(video_id)}/speakers/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WithdrawnResponse, + parse_obj_as( + type_=WithdrawnResponse, # 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( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # 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/arcmira/transcripts/types/__init__.py b/src/arcmira/transcripts/types/__init__.py new file mode 100644 index 0000000..ccf092c --- /dev/null +++ b/src/arcmira/transcripts/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 .captions_transcripts_request_src import CaptionsTranscriptsRequestSrc + from .get_transcripts_request_quality import GetTranscriptsRequestQuality + from .get_transcripts_request_src import GetTranscriptsRequestSrc + from .list_requests_transcripts_request_src import ListRequestsTranscriptsRequestSrc + from .search_transcripts_request_source import SearchTranscriptsRequestSource + from .search_transcripts_request_src import SearchTranscriptsRequestSrc + from .status_transcripts_request_src import StatusTranscriptsRequestSrc +_dynamic_imports: typing.Dict[str, str] = { + "CaptionsTranscriptsRequestSrc": ".captions_transcripts_request_src", + "GetTranscriptsRequestQuality": ".get_transcripts_request_quality", + "GetTranscriptsRequestSrc": ".get_transcripts_request_src", + "ListRequestsTranscriptsRequestSrc": ".list_requests_transcripts_request_src", + "SearchTranscriptsRequestSource": ".search_transcripts_request_source", + "SearchTranscriptsRequestSrc": ".search_transcripts_request_src", + "StatusTranscriptsRequestSrc": ".status_transcripts_request_src", +} + + +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__ = [ + "CaptionsTranscriptsRequestSrc", + "GetTranscriptsRequestQuality", + "GetTranscriptsRequestSrc", + "ListRequestsTranscriptsRequestSrc", + "SearchTranscriptsRequestSource", + "SearchTranscriptsRequestSrc", + "StatusTranscriptsRequestSrc", +] diff --git a/src/arcmira/transcripts/types/captions_transcripts_request_src.py b/src/arcmira/transcripts/types/captions_transcripts_request_src.py new file mode 100644 index 0000000..a7d335e --- /dev/null +++ b/src/arcmira/transcripts/types/captions_transcripts_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CaptionsTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/get_transcripts_request_quality.py b/src/arcmira/transcripts/types/get_transcripts_request_quality.py new file mode 100644 index 0000000..d84f058 --- /dev/null +++ b/src/arcmira/transcripts/types/get_transcripts_request_quality.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GetTranscriptsRequestQuality = typing.Union[typing.Literal["captions", "premium"], typing.Any] diff --git a/src/arcmira/transcripts/types/get_transcripts_request_src.py b/src/arcmira/transcripts/types/get_transcripts_request_src.py new file mode 100644 index 0000000..ff73274 --- /dev/null +++ b/src/arcmira/transcripts/types/get_transcripts_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GetTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/list_requests_transcripts_request_src.py b/src/arcmira/transcripts/types/list_requests_transcripts_request_src.py new file mode 100644 index 0000000..c8e2587 --- /dev/null +++ b/src/arcmira/transcripts/types/list_requests_transcripts_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListRequestsTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/search_transcripts_request_source.py b/src/arcmira/transcripts/types/search_transcripts_request_source.py new file mode 100644 index 0000000..ceb756c --- /dev/null +++ b/src/arcmira/transcripts/types/search_transcripts_request_source.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SearchTranscriptsRequestSource = typing.Union[ + typing.Literal["arcmira_premium", "creator_captions", "third_party_quick"], typing.Any +] diff --git a/src/arcmira/transcripts/types/search_transcripts_request_src.py b/src/arcmira/transcripts/types/search_transcripts_request_src.py new file mode 100644 index 0000000..c998c06 --- /dev/null +++ b/src/arcmira/transcripts/types/search_transcripts_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SearchTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/status_transcripts_request_src.py b/src/arcmira/transcripts/types/status_transcripts_request_src.py new file mode 100644 index 0000000..92591cc --- /dev/null +++ b/src/arcmira/transcripts/types/status_transcripts_request_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +StatusTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/types/__init__.py b/src/arcmira/types/__init__.py new file mode 100644 index 0000000..92f2fb0 --- /dev/null +++ b/src/arcmira/types/__init__.py @@ -0,0 +1,1239 @@ +# 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 .account_settings import AccountSettings + from .alert import Alert + from .alert_evidence_kind import AlertEvidenceKind + from .alert_list_response import AlertListResponse + from .alert_monitor import AlertMonitor + from .alert_tracker import AlertTracker + from .bad_ranking_change import BadRankingChange + from .caption_track import CaptionTrack + from .channel_coverage_response import ChannelCoverageResponse + from .channel_coverage_response_channel import ChannelCoverageResponseChannel + from .channel_coverage_response_channel_source_mix import ChannelCoverageResponseChannelSourceMix + from .channel_guest_list_response import ChannelGuestListResponse + from .channel_guest_list_response_export_capabilities import ChannelGuestListResponseExportCapabilities + from .channel_guest_list_response_items_item import ChannelGuestListResponseItemsItem + from .channel_guest_list_response_items_item_sentiment import ChannelGuestListResponseItemsItemSentiment + from .channel_page_response import ChannelPageResponse + from .channel_page_response_channel_info import ChannelPageResponseChannelInfo + from .channel_page_response_entity import ChannelPageResponseEntity + from .channel_page_response_entity_owner import ChannelPageResponseEntityOwner + from .channel_page_response_entity_type import ChannelPageResponseEntityType + from .channel_page_response_episodes_by_month_item import ChannelPageResponseEpisodesByMonthItem + from .channel_page_response_episodes_item import ChannelPageResponseEpisodesItem + from .channel_page_response_episodes_item_platform import ChannelPageResponseEpisodesItemPlatform + from .channel_page_response_episodes_item_sentiment import ChannelPageResponseEpisodesItemSentiment + from .channel_page_response_episodes_item_timestamp import ChannelPageResponseEpisodesItemTimestamp + from .channel_page_response_episodes_item_type import ChannelPageResponseEpisodesItemType + from .channel_page_response_guests_item import ChannelPageResponseGuestsItem + from .channel_page_response_guests_item_role import ChannelPageResponseGuestsItemRole + from .channel_page_response_guests_item_sentiment import ChannelPageResponseGuestsItemSentiment + from .channel_page_response_hosts_detailed_item import ChannelPageResponseHostsDetailedItem + from .channel_page_response_hosts_detailed_item_sentiment import ChannelPageResponseHostsDetailedItemSentiment + from .channel_page_response_organizations_item import ChannelPageResponseOrganizationsItem + from .channel_page_response_organizations_item_sentiment import ChannelPageResponseOrganizationsItemSentiment + from .channel_page_response_products_item import ChannelPageResponseProductsItem + from .channel_page_response_products_item_sentiment import ChannelPageResponseProductsItemSentiment + from .channel_page_response_recommendations_summary import ChannelPageResponseRecommendationsSummary + from .channel_page_response_stats import ChannelPageResponseStats + from .channel_page_response_topics_item import ChannelPageResponseTopicsItem + from .channel_page_response_topics_item_sentiment import ChannelPageResponseTopicsItemSentiment + from .channel_sponsor import ChannelSponsor + from .channel_sponsor_entity import ChannelSponsorEntity + from .channel_sponsor_sponsor_status import ChannelSponsorSponsorStatus + from .channel_sponsors_response import ChannelSponsorsResponse + from .channel_sponsors_response_access import ChannelSponsorsResponseAccess + from .channel_sponsors_response_access_gate import ChannelSponsorsResponseAccessGate + from .channel_sponsors_response_access_reason import ChannelSponsorsResponseAccessReason + from .channel_sponsors_response_access_type import ChannelSponsorsResponseAccessType + from .channel_sponsors_response_access_unlock import ChannelSponsorsResponseAccessUnlock + from .channel_sponsors_response_access_unlock_action import ChannelSponsorsResponseAccessUnlockAction + from .channel_sponsors_response_channel import ChannelSponsorsResponseChannel + from .channel_sponsors_response_meta import ChannelSponsorsResponseMeta + from .channel_videos_response import ChannelVideosResponse + from .channel_videos_response_channel import ChannelVideosResponseChannel + from .channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem + from .correction_accepted_response import CorrectionAcceptedResponse + from .correction_accepted_response_kind import CorrectionAcceptedResponseKind + from .correction_seq_mismatch_response import CorrectionSeqMismatchResponse + from .delivery_issue_change import DeliveryIssueChange + from .delivery_issue_change_channel import DeliveryIssueChangeChannel + from .entity import Entity + from .entity_card import EntityCard + from .entity_cards_response import EntityCardsResponse + from .entity_channel_list_response import EntityChannelListResponse + from .entity_channel_list_response_export_capabilities import EntityChannelListResponseExportCapabilities + from .entity_channel_list_response_items_item import EntityChannelListResponseItemsItem + from .entity_detail_recommendations_summary import EntityDetailRecommendationsSummary + from .entity_detail_response import EntityDetailResponse + from .entity_lookup_response import EntityLookupResponse + from .entity_momentum_response import EntityMomentumResponse + from .entity_momentum_response_access import EntityMomentumResponseAccess + from .entity_momentum_response_access_gate import EntityMomentumResponseAccessGate + from .entity_momentum_response_access_reason import EntityMomentumResponseAccessReason + from .entity_momentum_response_access_type import EntityMomentumResponseAccessType + from .entity_momentum_response_access_unlock import EntityMomentumResponseAccessUnlock + from .entity_momentum_response_access_unlock_action import EntityMomentumResponseAccessUnlockAction + from .entity_momentum_response_paid_vs_organic import EntityMomentumResponsePaidVsOrganic + from .entity_momentum_response_top_shows_item import EntityMomentumResponseTopShowsItem + from .entity_momentum_response_verdict import EntityMomentumResponseVerdict + from .entity_momentum_response_volume import EntityMomentumResponseVolume + from .entity_organization_list_response import EntityOrganizationListResponse + from .entity_organization_list_response_export_capabilities import EntityOrganizationListResponseExportCapabilities + from .entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem + from .entity_organization_list_response_items_item_sentiment import EntityOrganizationListResponseItemsItemSentiment + from .entity_page_mention import EntityPageMention + from .entity_page_mention_excerpt import EntityPageMentionExcerpt + from .entity_page_mention_excerpt_public_source_class import EntityPageMentionExcerptPublicSourceClass + from .entity_page_mention_platform import EntityPageMentionPlatform + from .entity_page_mention_sentiment import EntityPageMentionSentiment + from .entity_page_mention_type import EntityPageMentionType + from .entity_people_list_response import EntityPeopleListResponse + from .entity_people_list_response_export_capabilities import EntityPeopleListResponseExportCapabilities + from .entity_people_list_response_items_item import EntityPeopleListResponseItemsItem + from .entity_people_list_response_items_item_sentiment import EntityPeopleListResponseItemsItemSentiment + from .entity_people_list_response_people_mode import EntityPeopleListResponsePeopleMode + from .entity_product_list_response import EntityProductListResponse + from .entity_product_list_response_export_capabilities import EntityProductListResponseExportCapabilities + from .entity_product_list_response_items_item import EntityProductListResponseItemsItem + from .entity_product_list_response_items_item_sentiment import EntityProductListResponseItemsItemSentiment + from .entity_ref import EntityRef + from .entity_resolve_response import EntityResolveResponse + from .entity_resolve_response_ask import EntityResolveResponseAsk + from .entity_resolve_response_ask_options_item import EntityResolveResponseAskOptionsItem + from .entity_resolve_response_confidence import EntityResolveResponseConfidence + from .entity_search_response import EntitySearchResponse + from .entity_search_result import EntitySearchResult + from .entity_search_result_recommendations_summary import EntitySearchResultRecommendationsSummary + from .entity_topic_list_response import EntityTopicListResponse + from .entity_topic_list_response_export_capabilities import EntityTopicListResponseExportCapabilities + from .entity_topic_list_response_items_item import EntityTopicListResponseItemsItem + from .entity_topic_list_response_items_item_sentiment import EntityTopicListResponseItemsItemSentiment + from .error import Error + from .error_error import ErrorError + from .error_error_gate import ErrorErrorGate + from .error_error_reason import ErrorErrorReason + from .error_error_type import ErrorErrorType + from .error_error_unlock import ErrorErrorUnlock + from .error_error_unlock_action import ErrorErrorUnlockAction + from .exposure_meta import ExposureMeta + from .exposure_meta_access import ExposureMetaAccess + from .exposure_meta_access_chart import ExposureMetaAccessChart + from .exposure_meta_access_cls import ExposureMetaAccessCls + from .exposure_meta_access_freshness import ExposureMetaAccessFreshness + from .exposure_meta_access_ladder import ExposureMetaAccessLadder + from .exposure_meta_access_rows import ExposureMetaAccessRows + from .exposure_meta_access_rows_entities import ExposureMetaAccessRowsEntities + from .exposure_meta_access_rows_media import ExposureMetaAccessRowsMedia + from .exposure_meta_access_rows_topics import ExposureMetaAccessRowsTopics + from .exposure_meta_access_unlock import ExposureMetaAccessUnlock + from .exposure_meta_access_unlock_limit_action import ExposureMetaAccessUnlockLimitAction + from .exposure_meta_access_unlock_src import ExposureMetaAccessUnlockSrc + from .exposure_meta_access_view import ExposureMetaAccessView + from .exposure_meta_access_withheld_item import ExposureMetaAccessWithheldItem + from .exposure_meta_access_withheld_item_kind import ExposureMetaAccessWithheldItemKind + from .exposure_meta_access_withheld_item_param import ExposureMetaAccessWithheldItemParam + from .exposure_meta_access_withheld_item_section import ExposureMetaAccessWithheldItemSection + from .exposure_meta_access_withheld_item_what import ExposureMetaAccessWithheldItemWhat + from .exposure_meta_credits import ExposureMetaCredits + from .exposure_meta_credits_on_demand import ExposureMetaCreditsOnDemand + from .exposure_meta_credits_plan import ExposureMetaCreditsPlan + from .exposure_meta_free_limit import ExposureMetaFreeLimit + from .exposure_meta_limit_action import ExposureMetaLimitAction + from .exposure_meta_limits import ExposureMetaLimits + from .exposure_meta_recent_preview import ExposureMetaRecentPreview + from .exposure_meta_recent_preview_experiment import ExposureMetaRecentPreviewExperiment + from .exposure_meta_recent_preview_mentions import ExposureMetaRecentPreviewMentions + from .exposure_meta_recent_preview_mentions_experiment import ExposureMetaRecentPreviewMentionsExperiment + from .exposure_meta_recent_preview_mentions_subject import ExposureMetaRecentPreviewMentionsSubject + from .exposure_meta_recent_preview_mentions_teaser_items_item import ( + ExposureMetaRecentPreviewMentionsTeaserItemsItem, + ) + from .exposure_meta_recent_preview_subject import ExposureMetaRecentPreviewSubject + from .exposure_meta_recent_preview_teaser_items_item import ExposureMetaRecentPreviewTeaserItemsItem + from .exposure_meta_totals import ExposureMetaTotals + from .exposure_meta_usage_limit_type import ExposureMetaUsageLimitType + from .feedback_correction_result import FeedbackCorrectionResult + from .feedback_correction_result_recommendation import FeedbackCorrectionResultRecommendation + from .feedback_correction_result_recommendation_media import FeedbackCorrectionResultRecommendationMedia + from .feedback_correction_result_recommendation_media_source_channel import ( + FeedbackCorrectionResultRecommendationMediaSourceChannel, + ) + from .feedback_correction_result_status import FeedbackCorrectionResultStatus + from .feedback_readback_correction import FeedbackReadbackCorrection + from .feedback_readback_correction_status import FeedbackReadbackCorrectionStatus + from .feedback_readback_response import FeedbackReadbackResponse + from .feedback_readback_response_status import FeedbackReadbackResponseStatus + from .feedback_response import FeedbackResponse + from .freeform_suggested_change import FreeformSuggestedChange + from .health_response import HealthResponse + from .health_response_status import HealthResponseStatus + from .health_response_version import HealthResponseVersion + from .me_response import MeResponse + from .me_response_credential_kind import MeResponseCredentialKind + from .me_response_usage import MeResponseUsage + from .me_response_usage_credits import MeResponseUsageCredits + from .me_response_usage_credits_on_demand import MeResponseUsageCreditsOnDemand + from .me_response_usage_credits_plan import MeResponseUsageCreditsPlan + from .me_response_usage_hits import MeResponseUsageHits + from .me_settings_response import MeSettingsResponse + from .mention import Mention + from .mention_counts_response import MentionCountsResponse + from .mention_counts_response_mode import MentionCountsResponseMode + from .mention_counts_response_rows_item import MentionCountsResponseRowsItem + from .mention_counts_response_shared_item import MentionCountsResponseSharedItem + from .mention_counts_response_shared_item_by_channel_item import MentionCountsResponseSharedItemByChannelItem + from .mention_list_response import MentionListResponse + from .mention_list_response_entity import MentionListResponseEntity + from .mention_list_response_unlock import MentionListResponseUnlock + from .mention_media import MentionMedia + from .mention_media_source_channel import MentionMediaSourceChannel + from .mention_recommendations import MentionRecommendations + from .mention_sentiment import MentionSentiment + from .merge_suggestion_change import MergeSuggestionChange + from .message_response import MessageResponse + from .missed_alert_change import MissedAlertChange + from .missing_result_change import MissingResultChange + from .monitor import Monitor + from .monitor_add_trackers_response import MonitorAddTrackersResponse + from .monitor_delete_response import MonitorDeleteResponse + from .monitor_email_recipients_item import MonitorEmailRecipientsItem + from .monitor_email_recipients_item_invitation_status import MonitorEmailRecipientsItemInvitationStatus + from .monitor_email_recipients_item_status import MonitorEmailRecipientsItemStatus + from .monitor_list_response import MonitorListResponse + from .monitor_list_response_monitors_item import MonitorListResponseMonitorsItem + from .monitor_list_response_monitors_item_slack_integration import MonitorListResponseMonitorsItemSlackIntegration + from .monitor_mutation_response import MonitorMutationResponse + from .monitor_mutation_response_monitor import MonitorMutationResponseMonitor + from .monitor_trackers_response import MonitorTrackersResponse + from .monitor_trackers_response_trackers_item import MonitorTrackersResponseTrackersItem + from .named_entity_ref import NamedEntityRef + from .open_api_document import OpenApiDocument + from .open_api_document_info import OpenApiDocumentInfo + from .open_api_document_info_contact import OpenApiDocumentInfoContact + from .open_api_document_servers_item import OpenApiDocumentServersItem + from .organization_page_response import OrganizationPageResponse + from .organization_page_response_channels_item import OrganizationPageResponseChannelsItem + from .organization_page_response_channels_item_sentiment import OrganizationPageResponseChannelsItemSentiment + from .organization_page_response_entity import OrganizationPageResponseEntity + from .organization_page_response_entity_owned_channels_item import OrganizationPageResponseEntityOwnedChannelsItem + from .organization_page_response_entity_owned_products_item import OrganizationPageResponseEntityOwnedProductsItem + from .organization_page_response_entity_type import OrganizationPageResponseEntityType + from .organization_page_response_mentions_by_month_item import OrganizationPageResponseMentionsByMonthItem + from .organization_page_response_people_item import OrganizationPageResponsePeopleItem + from .organization_page_response_people_item_sentiment import OrganizationPageResponsePeopleItemSentiment + from .organization_page_response_products_item import OrganizationPageResponseProductsItem + from .organization_page_response_products_item_sentiment import OrganizationPageResponseProductsItemSentiment + from .organization_page_response_role_edge import OrganizationPageResponseRoleEdge + from .organization_page_response_role_edge_label import OrganizationPageResponseRoleEdgeLabel + from .organization_page_response_role_edge_people_item import OrganizationPageResponseRoleEdgePeopleItem + from .organization_page_response_role_edge_recent_appearances_item import ( + OrganizationPageResponseRoleEdgeRecentAppearancesItem, + ) + from .organization_page_response_role_edge_role import OrganizationPageResponseRoleEdgeRole + from .organization_page_response_stats import OrganizationPageResponseStats + from .organization_page_response_topics_item import OrganizationPageResponseTopicsItem + from .organization_page_response_topics_item_sentiment import OrganizationPageResponseTopicsItemSentiment + from .person_appearance_list_response import PersonAppearanceListResponse + from .person_appearance_list_response_items_item import PersonAppearanceListResponseItemsItem + from .person_appearance_list_response_items_item_platform import PersonAppearanceListResponseItemsItemPlatform + from .person_appearance_list_response_items_item_sentiment import PersonAppearanceListResponseItemsItemSentiment + from .person_appearance_list_response_items_item_type import PersonAppearanceListResponseItemsItemType + from .person_page_response import PersonPageResponse + from .person_page_response_appearances_by_month_item import PersonPageResponseAppearancesByMonthItem + from .person_page_response_appearances_item import PersonPageResponseAppearancesItem + from .person_page_response_appearances_item_platform import PersonPageResponseAppearancesItemPlatform + from .person_page_response_appearances_item_sentiment import PersonPageResponseAppearancesItemSentiment + from .person_page_response_appearances_item_type import PersonPageResponseAppearancesItemType + from .person_page_response_brands_item import PersonPageResponseBrandsItem + from .person_page_response_brands_item_sentiment import PersonPageResponseBrandsItemSentiment + from .person_page_response_entity import PersonPageResponseEntity + from .person_page_response_entity_owned_channels_item import PersonPageResponseEntityOwnedChannelsItem + from .person_page_response_entity_owned_products_item import PersonPageResponseEntityOwnedProductsItem + from .person_page_response_mentions_by_month_item import PersonPageResponseMentionsByMonthItem + from .person_page_response_mentions_item import PersonPageResponseMentionsItem + from .person_page_response_mentions_item_platform import PersonPageResponseMentionsItemPlatform + from .person_page_response_mentions_item_sentiment import PersonPageResponseMentionsItemSentiment + from .person_page_response_mentions_item_type import PersonPageResponseMentionsItemType + from .person_page_response_people_item import PersonPageResponsePeopleItem + from .person_page_response_people_item_role import PersonPageResponsePeopleItemRole + from .person_page_response_people_item_sentiment import PersonPageResponsePeopleItemSentiment + from .person_page_response_products_item import PersonPageResponseProductsItem + from .person_page_response_products_item_sentiment import PersonPageResponseProductsItemSentiment + from .person_page_response_role_edge import PersonPageResponseRoleEdge + from .person_page_response_role_edge_label import PersonPageResponseRoleEdgeLabel + from .person_page_response_role_edge_role import PersonPageResponseRoleEdgeRole + from .person_page_response_stats import PersonPageResponseStats + from .person_page_response_topics_item import PersonPageResponseTopicsItem + from .person_page_response_topics_item_sentiment import PersonPageResponseTopicsItemSentiment + from .product_page_response import ProductPageResponse + from .product_page_response_channels_item import ProductPageResponseChannelsItem + from .product_page_response_channels_item_sentiment import ProductPageResponseChannelsItemSentiment + from .product_page_response_entity import ProductPageResponseEntity + from .product_page_response_entity_owner import ProductPageResponseEntityOwner + from .product_page_response_entity_parent_org import ProductPageResponseEntityParentOrg + from .product_page_response_entity_type import ProductPageResponseEntityType + from .product_page_response_mentions_by_month_item import ProductPageResponseMentionsByMonthItem + from .product_page_response_opportunities import ProductPageResponseOpportunities + from .product_page_response_organizations_item import ProductPageResponseOrganizationsItem + from .product_page_response_organizations_item_sentiment import ProductPageResponseOrganizationsItemSentiment + from .product_page_response_people_item import ProductPageResponsePeopleItem + from .product_page_response_people_item_sentiment import ProductPageResponsePeopleItemSentiment + from .product_page_response_stats import ProductPageResponseStats + from .product_page_response_topics_item import ProductPageResponseTopicsItem + from .product_page_response_topics_item_sentiment import ProductPageResponseTopicsItemSentiment + from .published_excerpt import PublishedExcerpt + from .published_excerpt_public_source_class import PublishedExcerptPublicSourceClass + from .recommendation import Recommendation + from .recommendation_enrichment_item import RecommendationEnrichmentItem + from .recommendation_list_response import RecommendationListResponse + from .recommendation_list_response_entity import RecommendationListResponseEntity + from .recommendation_media import RecommendationMedia + from .recommendation_media_source_channel import RecommendationMediaSourceChannel + from .resolve_candidate import ResolveCandidate + from .resolve_candidate_match import ResolveCandidateMatch + from .resolve_suggestion import ResolveSuggestion + from .resolve_suggestion_match import ResolveSuggestionMatch + from .resolve_suggestion_reason import ResolveSuggestionReason + from .search_request_type import SearchRequestType + from .search_resolve_response import SearchResolveResponse + from .search_resolve_response_entity import SearchResolveResponseEntity + from .signup_sent_response import SignupSentResponse + from .signup_sent_response_next import SignupSentResponseNext + from .signup_sent_response_next_method import SignupSentResponseNextMethod + from .signup_verified_response import SignupVerifiedResponse + from .speaker_identification_submitted_response import SpeakerIdentificationSubmittedResponse + from .speaker_identification_submitted_response_identification import ( + SpeakerIdentificationSubmittedResponseIdentification, + ) + from .speaker_identification_submitted_response_identification_entity import ( + SpeakerIdentificationSubmittedResponseIdentificationEntity, + ) + from .speaker_identification_submitted_response_identification_status import ( + SpeakerIdentificationSubmittedResponseIdentificationStatus, + ) + from .stale_metadata_change import StaleMetadataChange + from .team_member import TeamMember + from .team_member_role import TeamMemberRole + from .team_member_seat_type import TeamMemberSeatType + from .team_member_spend import TeamMemberSpend + from .team_member_spend_role import TeamMemberSpendRole + from .team_member_spend_seat_type import TeamMemberSpendSeatType + from .team_members_response import TeamMembersResponse + from .team_members_response_team import TeamMembersResponseTeam + from .team_spend_response import TeamSpendResponse + from .team_usage_event import TeamUsageEvent + from .team_usage_events_response import TeamUsageEventsResponse + from .topic_page_response import TopicPageResponse + from .topic_page_response_channels_item import TopicPageResponseChannelsItem + from .topic_page_response_channels_item_sentiment import TopicPageResponseChannelsItemSentiment + from .topic_page_response_companies_item import TopicPageResponseCompaniesItem + from .topic_page_response_companies_item_sentiment import TopicPageResponseCompaniesItemSentiment + from .topic_page_response_entity import TopicPageResponseEntity + from .topic_page_response_entity_type import TopicPageResponseEntityType + from .topic_page_response_mentions_by_month_item import TopicPageResponseMentionsByMonthItem + from .topic_page_response_products_item import TopicPageResponseProductsItem + from .topic_page_response_products_item_sentiment import TopicPageResponseProductsItemSentiment + from .topic_page_response_related_topics_item import TopicPageResponseRelatedTopicsItem + from .topic_page_response_related_topics_item_sentiment import TopicPageResponseRelatedTopicsItemSentiment + from .topic_page_response_stats import TopicPageResponseStats + from .topic_page_response_voices_item import TopicPageResponseVoicesItem + from .topic_page_response_voices_item_role import TopicPageResponseVoicesItemRole + from .topic_page_response_voices_item_sentiment import TopicPageResponseVoicesItemSentiment + from .tracker import Tracker + from .tracker_list_response import TrackerListResponse + from .tracker_mutation_response import TrackerMutationResponse + from .transcript_edit_submitted_response import TranscriptEditSubmittedResponse + from .transcript_edit_submitted_response_edit import TranscriptEditSubmittedResponseEdit + from .transcript_edit_submitted_response_edit_status import TranscriptEditSubmittedResponseEditStatus + from .transcript_pending import TranscriptPending + from .transcript_pending_premium_job import TranscriptPendingPremiumJob + from .transcript_pending_quality import TranscriptPendingQuality + from .transcript_purchase_quote import TranscriptPurchaseQuote + from .transcript_purchase_quote_billing_scope import TranscriptPurchaseQuoteBillingScope + from .transcript_purchase_quote_charge import TranscriptPurchaseQuoteCharge + from .transcript_purchase_quote_charge_unit import TranscriptPurchaseQuoteChargeUnit + from .transcript_quote import TranscriptQuote + from .transcript_response import TranscriptResponse + from .transcript_response_access import TranscriptResponseAccess + from .transcript_response_access_gate import TranscriptResponseAccessGate + from .transcript_response_access_reason import TranscriptResponseAccessReason + from .transcript_response_access_type import TranscriptResponseAccessType + from .transcript_response_access_unlock import TranscriptResponseAccessUnlock + from .transcript_response_access_unlock_action import TranscriptResponseAccessUnlockAction + from .transcript_response_lines_item import TranscriptResponseLinesItem + from .transcript_response_paragraphs_item import TranscriptResponseParagraphsItem + from .transcript_response_premium_job import TranscriptResponsePremiumJob + from .transcript_response_quality import TranscriptResponseQuality + from .transcript_response_range import TranscriptResponseRange + from .transcript_response_source import TranscriptResponseSource + from .transcript_response_speakers_item import TranscriptResponseSpeakersItem + from .transcript_result import TranscriptResult, TranscriptResult_Pending, TranscriptResult_Ready + from .transcript_search_chunk import TranscriptSearchChunk + from .transcript_search_response import TranscriptSearchResponse + from .transcript_search_response_access import TranscriptSearchResponseAccess + from .transcript_search_response_access_gate import TranscriptSearchResponseAccessGate + from .transcript_search_response_access_reason import TranscriptSearchResponseAccessReason + from .transcript_search_response_access_type import TranscriptSearchResponseAccessType + from .transcript_search_response_access_unlock import TranscriptSearchResponseAccessUnlock + from .transcript_search_response_access_unlock_action import TranscriptSearchResponseAccessUnlockAction + from .transcript_search_response_filters import TranscriptSearchResponseFilters + from .transcript_search_response_search_index import TranscriptSearchResponseSearchIndex + from .transcript_search_response_search_index_state import TranscriptSearchResponseSearchIndexState + from .transcript_settings import TranscriptSettings + from .transcript_settings_quality import TranscriptSettingsQuality + from .transcript_video import TranscriptVideo + from .transcription_list_response import TranscriptionListResponse + from .transcription_list_response_requests_item import TranscriptionListResponseRequestsItem + from .transcription_request import TranscriptionRequest + from .transcription_request_charge import TranscriptionRequestCharge + from .transcription_request_charge_unit import TranscriptionRequestChargeUnit + from .transcription_request_quote import TranscriptionRequestQuote + from .transcription_request_stage import TranscriptionRequestStage + from .transcription_request_state import TranscriptionRequestState + from .transcription_request_status import TranscriptionRequestStatus + from .transcription_submit_response import TranscriptionSubmitResponse + from .video_captions_response import VideoCaptionsResponse + from .video_merge_list_response import VideoMergeListResponse + from .video_merge_list_response_merges_item import VideoMergeListResponseMergesItem + from .video_merge_list_response_merges_item_status import VideoMergeListResponseMergesItemStatus + from .video_merge_submitted_response import VideoMergeSubmittedResponse + from .video_merge_submitted_response_merge import VideoMergeSubmittedResponseMerge + from .video_merge_submitted_response_merge_status import VideoMergeSubmittedResponseMergeStatus + from .webhook_secret_rotate_response import WebhookSecretRotateResponse + from .withdrawn_response import WithdrawnResponse + from .wrong_classification_change import WrongClassificationChange + from .wrong_classification_change_mention_class import WrongClassificationChangeMentionClass + from .wrong_entity_change import WrongEntityChange + from .wrong_entity_type_change import WrongEntityTypeChange + from .wrong_entity_type_change_field import WrongEntityTypeChangeField +_dynamic_imports: typing.Dict[str, str] = { + "AccountSettings": ".account_settings", + "Alert": ".alert", + "AlertEvidenceKind": ".alert_evidence_kind", + "AlertListResponse": ".alert_list_response", + "AlertMonitor": ".alert_monitor", + "AlertTracker": ".alert_tracker", + "BadRankingChange": ".bad_ranking_change", + "CaptionTrack": ".caption_track", + "ChannelCoverageResponse": ".channel_coverage_response", + "ChannelCoverageResponseChannel": ".channel_coverage_response_channel", + "ChannelCoverageResponseChannelSourceMix": ".channel_coverage_response_channel_source_mix", + "ChannelGuestListResponse": ".channel_guest_list_response", + "ChannelGuestListResponseExportCapabilities": ".channel_guest_list_response_export_capabilities", + "ChannelGuestListResponseItemsItem": ".channel_guest_list_response_items_item", + "ChannelGuestListResponseItemsItemSentiment": ".channel_guest_list_response_items_item_sentiment", + "ChannelPageResponse": ".channel_page_response", + "ChannelPageResponseChannelInfo": ".channel_page_response_channel_info", + "ChannelPageResponseEntity": ".channel_page_response_entity", + "ChannelPageResponseEntityOwner": ".channel_page_response_entity_owner", + "ChannelPageResponseEntityType": ".channel_page_response_entity_type", + "ChannelPageResponseEpisodesByMonthItem": ".channel_page_response_episodes_by_month_item", + "ChannelPageResponseEpisodesItem": ".channel_page_response_episodes_item", + "ChannelPageResponseEpisodesItemPlatform": ".channel_page_response_episodes_item_platform", + "ChannelPageResponseEpisodesItemSentiment": ".channel_page_response_episodes_item_sentiment", + "ChannelPageResponseEpisodesItemTimestamp": ".channel_page_response_episodes_item_timestamp", + "ChannelPageResponseEpisodesItemType": ".channel_page_response_episodes_item_type", + "ChannelPageResponseGuestsItem": ".channel_page_response_guests_item", + "ChannelPageResponseGuestsItemRole": ".channel_page_response_guests_item_role", + "ChannelPageResponseGuestsItemSentiment": ".channel_page_response_guests_item_sentiment", + "ChannelPageResponseHostsDetailedItem": ".channel_page_response_hosts_detailed_item", + "ChannelPageResponseHostsDetailedItemSentiment": ".channel_page_response_hosts_detailed_item_sentiment", + "ChannelPageResponseOrganizationsItem": ".channel_page_response_organizations_item", + "ChannelPageResponseOrganizationsItemSentiment": ".channel_page_response_organizations_item_sentiment", + "ChannelPageResponseProductsItem": ".channel_page_response_products_item", + "ChannelPageResponseProductsItemSentiment": ".channel_page_response_products_item_sentiment", + "ChannelPageResponseRecommendationsSummary": ".channel_page_response_recommendations_summary", + "ChannelPageResponseStats": ".channel_page_response_stats", + "ChannelPageResponseTopicsItem": ".channel_page_response_topics_item", + "ChannelPageResponseTopicsItemSentiment": ".channel_page_response_topics_item_sentiment", + "ChannelSponsor": ".channel_sponsor", + "ChannelSponsorEntity": ".channel_sponsor_entity", + "ChannelSponsorSponsorStatus": ".channel_sponsor_sponsor_status", + "ChannelSponsorsResponse": ".channel_sponsors_response", + "ChannelSponsorsResponseAccess": ".channel_sponsors_response_access", + "ChannelSponsorsResponseAccessGate": ".channel_sponsors_response_access_gate", + "ChannelSponsorsResponseAccessReason": ".channel_sponsors_response_access_reason", + "ChannelSponsorsResponseAccessType": ".channel_sponsors_response_access_type", + "ChannelSponsorsResponseAccessUnlock": ".channel_sponsors_response_access_unlock", + "ChannelSponsorsResponseAccessUnlockAction": ".channel_sponsors_response_access_unlock_action", + "ChannelSponsorsResponseChannel": ".channel_sponsors_response_channel", + "ChannelSponsorsResponseMeta": ".channel_sponsors_response_meta", + "ChannelVideosResponse": ".channel_videos_response", + "ChannelVideosResponseChannel": ".channel_videos_response_channel", + "ChannelVideosResponseEpisodesItem": ".channel_videos_response_episodes_item", + "CorrectionAcceptedResponse": ".correction_accepted_response", + "CorrectionAcceptedResponseKind": ".correction_accepted_response_kind", + "CorrectionSeqMismatchResponse": ".correction_seq_mismatch_response", + "DeliveryIssueChange": ".delivery_issue_change", + "DeliveryIssueChangeChannel": ".delivery_issue_change_channel", + "Entity": ".entity", + "EntityCard": ".entity_card", + "EntityCardsResponse": ".entity_cards_response", + "EntityChannelListResponse": ".entity_channel_list_response", + "EntityChannelListResponseExportCapabilities": ".entity_channel_list_response_export_capabilities", + "EntityChannelListResponseItemsItem": ".entity_channel_list_response_items_item", + "EntityDetailRecommendationsSummary": ".entity_detail_recommendations_summary", + "EntityDetailResponse": ".entity_detail_response", + "EntityLookupResponse": ".entity_lookup_response", + "EntityMomentumResponse": ".entity_momentum_response", + "EntityMomentumResponseAccess": ".entity_momentum_response_access", + "EntityMomentumResponseAccessGate": ".entity_momentum_response_access_gate", + "EntityMomentumResponseAccessReason": ".entity_momentum_response_access_reason", + "EntityMomentumResponseAccessType": ".entity_momentum_response_access_type", + "EntityMomentumResponseAccessUnlock": ".entity_momentum_response_access_unlock", + "EntityMomentumResponseAccessUnlockAction": ".entity_momentum_response_access_unlock_action", + "EntityMomentumResponsePaidVsOrganic": ".entity_momentum_response_paid_vs_organic", + "EntityMomentumResponseTopShowsItem": ".entity_momentum_response_top_shows_item", + "EntityMomentumResponseVerdict": ".entity_momentum_response_verdict", + "EntityMomentumResponseVolume": ".entity_momentum_response_volume", + "EntityOrganizationListResponse": ".entity_organization_list_response", + "EntityOrganizationListResponseExportCapabilities": ".entity_organization_list_response_export_capabilities", + "EntityOrganizationListResponseItemsItem": ".entity_organization_list_response_items_item", + "EntityOrganizationListResponseItemsItemSentiment": ".entity_organization_list_response_items_item_sentiment", + "EntityPageMention": ".entity_page_mention", + "EntityPageMentionExcerpt": ".entity_page_mention_excerpt", + "EntityPageMentionExcerptPublicSourceClass": ".entity_page_mention_excerpt_public_source_class", + "EntityPageMentionPlatform": ".entity_page_mention_platform", + "EntityPageMentionSentiment": ".entity_page_mention_sentiment", + "EntityPageMentionType": ".entity_page_mention_type", + "EntityPeopleListResponse": ".entity_people_list_response", + "EntityPeopleListResponseExportCapabilities": ".entity_people_list_response_export_capabilities", + "EntityPeopleListResponseItemsItem": ".entity_people_list_response_items_item", + "EntityPeopleListResponseItemsItemSentiment": ".entity_people_list_response_items_item_sentiment", + "EntityPeopleListResponsePeopleMode": ".entity_people_list_response_people_mode", + "EntityProductListResponse": ".entity_product_list_response", + "EntityProductListResponseExportCapabilities": ".entity_product_list_response_export_capabilities", + "EntityProductListResponseItemsItem": ".entity_product_list_response_items_item", + "EntityProductListResponseItemsItemSentiment": ".entity_product_list_response_items_item_sentiment", + "EntityRef": ".entity_ref", + "EntityResolveResponse": ".entity_resolve_response", + "EntityResolveResponseAsk": ".entity_resolve_response_ask", + "EntityResolveResponseAskOptionsItem": ".entity_resolve_response_ask_options_item", + "EntityResolveResponseConfidence": ".entity_resolve_response_confidence", + "EntitySearchResponse": ".entity_search_response", + "EntitySearchResult": ".entity_search_result", + "EntitySearchResultRecommendationsSummary": ".entity_search_result_recommendations_summary", + "EntityTopicListResponse": ".entity_topic_list_response", + "EntityTopicListResponseExportCapabilities": ".entity_topic_list_response_export_capabilities", + "EntityTopicListResponseItemsItem": ".entity_topic_list_response_items_item", + "EntityTopicListResponseItemsItemSentiment": ".entity_topic_list_response_items_item_sentiment", + "Error": ".error", + "ErrorError": ".error_error", + "ErrorErrorGate": ".error_error_gate", + "ErrorErrorReason": ".error_error_reason", + "ErrorErrorType": ".error_error_type", + "ErrorErrorUnlock": ".error_error_unlock", + "ErrorErrorUnlockAction": ".error_error_unlock_action", + "ExposureMeta": ".exposure_meta", + "ExposureMetaAccess": ".exposure_meta_access", + "ExposureMetaAccessChart": ".exposure_meta_access_chart", + "ExposureMetaAccessCls": ".exposure_meta_access_cls", + "ExposureMetaAccessFreshness": ".exposure_meta_access_freshness", + "ExposureMetaAccessLadder": ".exposure_meta_access_ladder", + "ExposureMetaAccessRows": ".exposure_meta_access_rows", + "ExposureMetaAccessRowsEntities": ".exposure_meta_access_rows_entities", + "ExposureMetaAccessRowsMedia": ".exposure_meta_access_rows_media", + "ExposureMetaAccessRowsTopics": ".exposure_meta_access_rows_topics", + "ExposureMetaAccessUnlock": ".exposure_meta_access_unlock", + "ExposureMetaAccessUnlockLimitAction": ".exposure_meta_access_unlock_limit_action", + "ExposureMetaAccessUnlockSrc": ".exposure_meta_access_unlock_src", + "ExposureMetaAccessView": ".exposure_meta_access_view", + "ExposureMetaAccessWithheldItem": ".exposure_meta_access_withheld_item", + "ExposureMetaAccessWithheldItemKind": ".exposure_meta_access_withheld_item_kind", + "ExposureMetaAccessWithheldItemParam": ".exposure_meta_access_withheld_item_param", + "ExposureMetaAccessWithheldItemSection": ".exposure_meta_access_withheld_item_section", + "ExposureMetaAccessWithheldItemWhat": ".exposure_meta_access_withheld_item_what", + "ExposureMetaCredits": ".exposure_meta_credits", + "ExposureMetaCreditsOnDemand": ".exposure_meta_credits_on_demand", + "ExposureMetaCreditsPlan": ".exposure_meta_credits_plan", + "ExposureMetaFreeLimit": ".exposure_meta_free_limit", + "ExposureMetaLimitAction": ".exposure_meta_limit_action", + "ExposureMetaLimits": ".exposure_meta_limits", + "ExposureMetaRecentPreview": ".exposure_meta_recent_preview", + "ExposureMetaRecentPreviewExperiment": ".exposure_meta_recent_preview_experiment", + "ExposureMetaRecentPreviewMentions": ".exposure_meta_recent_preview_mentions", + "ExposureMetaRecentPreviewMentionsExperiment": ".exposure_meta_recent_preview_mentions_experiment", + "ExposureMetaRecentPreviewMentionsSubject": ".exposure_meta_recent_preview_mentions_subject", + "ExposureMetaRecentPreviewMentionsTeaserItemsItem": ".exposure_meta_recent_preview_mentions_teaser_items_item", + "ExposureMetaRecentPreviewSubject": ".exposure_meta_recent_preview_subject", + "ExposureMetaRecentPreviewTeaserItemsItem": ".exposure_meta_recent_preview_teaser_items_item", + "ExposureMetaTotals": ".exposure_meta_totals", + "ExposureMetaUsageLimitType": ".exposure_meta_usage_limit_type", + "FeedbackCorrectionResult": ".feedback_correction_result", + "FeedbackCorrectionResultRecommendation": ".feedback_correction_result_recommendation", + "FeedbackCorrectionResultRecommendationMedia": ".feedback_correction_result_recommendation_media", + "FeedbackCorrectionResultRecommendationMediaSourceChannel": ".feedback_correction_result_recommendation_media_source_channel", + "FeedbackCorrectionResultStatus": ".feedback_correction_result_status", + "FeedbackReadbackCorrection": ".feedback_readback_correction", + "FeedbackReadbackCorrectionStatus": ".feedback_readback_correction_status", + "FeedbackReadbackResponse": ".feedback_readback_response", + "FeedbackReadbackResponseStatus": ".feedback_readback_response_status", + "FeedbackResponse": ".feedback_response", + "FreeformSuggestedChange": ".freeform_suggested_change", + "HealthResponse": ".health_response", + "HealthResponseStatus": ".health_response_status", + "HealthResponseVersion": ".health_response_version", + "MeResponse": ".me_response", + "MeResponseCredentialKind": ".me_response_credential_kind", + "MeResponseUsage": ".me_response_usage", + "MeResponseUsageCredits": ".me_response_usage_credits", + "MeResponseUsageCreditsOnDemand": ".me_response_usage_credits_on_demand", + "MeResponseUsageCreditsPlan": ".me_response_usage_credits_plan", + "MeResponseUsageHits": ".me_response_usage_hits", + "MeSettingsResponse": ".me_settings_response", + "Mention": ".mention", + "MentionCountsResponse": ".mention_counts_response", + "MentionCountsResponseMode": ".mention_counts_response_mode", + "MentionCountsResponseRowsItem": ".mention_counts_response_rows_item", + "MentionCountsResponseSharedItem": ".mention_counts_response_shared_item", + "MentionCountsResponseSharedItemByChannelItem": ".mention_counts_response_shared_item_by_channel_item", + "MentionListResponse": ".mention_list_response", + "MentionListResponseEntity": ".mention_list_response_entity", + "MentionListResponseUnlock": ".mention_list_response_unlock", + "MentionMedia": ".mention_media", + "MentionMediaSourceChannel": ".mention_media_source_channel", + "MentionRecommendations": ".mention_recommendations", + "MentionSentiment": ".mention_sentiment", + "MergeSuggestionChange": ".merge_suggestion_change", + "MessageResponse": ".message_response", + "MissedAlertChange": ".missed_alert_change", + "MissingResultChange": ".missing_result_change", + "Monitor": ".monitor", + "MonitorAddTrackersResponse": ".monitor_add_trackers_response", + "MonitorDeleteResponse": ".monitor_delete_response", + "MonitorEmailRecipientsItem": ".monitor_email_recipients_item", + "MonitorEmailRecipientsItemInvitationStatus": ".monitor_email_recipients_item_invitation_status", + "MonitorEmailRecipientsItemStatus": ".monitor_email_recipients_item_status", + "MonitorListResponse": ".monitor_list_response", + "MonitorListResponseMonitorsItem": ".monitor_list_response_monitors_item", + "MonitorListResponseMonitorsItemSlackIntegration": ".monitor_list_response_monitors_item_slack_integration", + "MonitorMutationResponse": ".monitor_mutation_response", + "MonitorMutationResponseMonitor": ".monitor_mutation_response_monitor", + "MonitorTrackersResponse": ".monitor_trackers_response", + "MonitorTrackersResponseTrackersItem": ".monitor_trackers_response_trackers_item", + "NamedEntityRef": ".named_entity_ref", + "OpenApiDocument": ".open_api_document", + "OpenApiDocumentInfo": ".open_api_document_info", + "OpenApiDocumentInfoContact": ".open_api_document_info_contact", + "OpenApiDocumentServersItem": ".open_api_document_servers_item", + "OrganizationPageResponse": ".organization_page_response", + "OrganizationPageResponseChannelsItem": ".organization_page_response_channels_item", + "OrganizationPageResponseChannelsItemSentiment": ".organization_page_response_channels_item_sentiment", + "OrganizationPageResponseEntity": ".organization_page_response_entity", + "OrganizationPageResponseEntityOwnedChannelsItem": ".organization_page_response_entity_owned_channels_item", + "OrganizationPageResponseEntityOwnedProductsItem": ".organization_page_response_entity_owned_products_item", + "OrganizationPageResponseEntityType": ".organization_page_response_entity_type", + "OrganizationPageResponseMentionsByMonthItem": ".organization_page_response_mentions_by_month_item", + "OrganizationPageResponsePeopleItem": ".organization_page_response_people_item", + "OrganizationPageResponsePeopleItemSentiment": ".organization_page_response_people_item_sentiment", + "OrganizationPageResponseProductsItem": ".organization_page_response_products_item", + "OrganizationPageResponseProductsItemSentiment": ".organization_page_response_products_item_sentiment", + "OrganizationPageResponseRoleEdge": ".organization_page_response_role_edge", + "OrganizationPageResponseRoleEdgeLabel": ".organization_page_response_role_edge_label", + "OrganizationPageResponseRoleEdgePeopleItem": ".organization_page_response_role_edge_people_item", + "OrganizationPageResponseRoleEdgeRecentAppearancesItem": ".organization_page_response_role_edge_recent_appearances_item", + "OrganizationPageResponseRoleEdgeRole": ".organization_page_response_role_edge_role", + "OrganizationPageResponseStats": ".organization_page_response_stats", + "OrganizationPageResponseTopicsItem": ".organization_page_response_topics_item", + "OrganizationPageResponseTopicsItemSentiment": ".organization_page_response_topics_item_sentiment", + "PersonAppearanceListResponse": ".person_appearance_list_response", + "PersonAppearanceListResponseItemsItem": ".person_appearance_list_response_items_item", + "PersonAppearanceListResponseItemsItemPlatform": ".person_appearance_list_response_items_item_platform", + "PersonAppearanceListResponseItemsItemSentiment": ".person_appearance_list_response_items_item_sentiment", + "PersonAppearanceListResponseItemsItemType": ".person_appearance_list_response_items_item_type", + "PersonPageResponse": ".person_page_response", + "PersonPageResponseAppearancesByMonthItem": ".person_page_response_appearances_by_month_item", + "PersonPageResponseAppearancesItem": ".person_page_response_appearances_item", + "PersonPageResponseAppearancesItemPlatform": ".person_page_response_appearances_item_platform", + "PersonPageResponseAppearancesItemSentiment": ".person_page_response_appearances_item_sentiment", + "PersonPageResponseAppearancesItemType": ".person_page_response_appearances_item_type", + "PersonPageResponseBrandsItem": ".person_page_response_brands_item", + "PersonPageResponseBrandsItemSentiment": ".person_page_response_brands_item_sentiment", + "PersonPageResponseEntity": ".person_page_response_entity", + "PersonPageResponseEntityOwnedChannelsItem": ".person_page_response_entity_owned_channels_item", + "PersonPageResponseEntityOwnedProductsItem": ".person_page_response_entity_owned_products_item", + "PersonPageResponseMentionsByMonthItem": ".person_page_response_mentions_by_month_item", + "PersonPageResponseMentionsItem": ".person_page_response_mentions_item", + "PersonPageResponseMentionsItemPlatform": ".person_page_response_mentions_item_platform", + "PersonPageResponseMentionsItemSentiment": ".person_page_response_mentions_item_sentiment", + "PersonPageResponseMentionsItemType": ".person_page_response_mentions_item_type", + "PersonPageResponsePeopleItem": ".person_page_response_people_item", + "PersonPageResponsePeopleItemRole": ".person_page_response_people_item_role", + "PersonPageResponsePeopleItemSentiment": ".person_page_response_people_item_sentiment", + "PersonPageResponseProductsItem": ".person_page_response_products_item", + "PersonPageResponseProductsItemSentiment": ".person_page_response_products_item_sentiment", + "PersonPageResponseRoleEdge": ".person_page_response_role_edge", + "PersonPageResponseRoleEdgeLabel": ".person_page_response_role_edge_label", + "PersonPageResponseRoleEdgeRole": ".person_page_response_role_edge_role", + "PersonPageResponseStats": ".person_page_response_stats", + "PersonPageResponseTopicsItem": ".person_page_response_topics_item", + "PersonPageResponseTopicsItemSentiment": ".person_page_response_topics_item_sentiment", + "ProductPageResponse": ".product_page_response", + "ProductPageResponseChannelsItem": ".product_page_response_channels_item", + "ProductPageResponseChannelsItemSentiment": ".product_page_response_channels_item_sentiment", + "ProductPageResponseEntity": ".product_page_response_entity", + "ProductPageResponseEntityOwner": ".product_page_response_entity_owner", + "ProductPageResponseEntityParentOrg": ".product_page_response_entity_parent_org", + "ProductPageResponseEntityType": ".product_page_response_entity_type", + "ProductPageResponseMentionsByMonthItem": ".product_page_response_mentions_by_month_item", + "ProductPageResponseOpportunities": ".product_page_response_opportunities", + "ProductPageResponseOrganizationsItem": ".product_page_response_organizations_item", + "ProductPageResponseOrganizationsItemSentiment": ".product_page_response_organizations_item_sentiment", + "ProductPageResponsePeopleItem": ".product_page_response_people_item", + "ProductPageResponsePeopleItemSentiment": ".product_page_response_people_item_sentiment", + "ProductPageResponseStats": ".product_page_response_stats", + "ProductPageResponseTopicsItem": ".product_page_response_topics_item", + "ProductPageResponseTopicsItemSentiment": ".product_page_response_topics_item_sentiment", + "PublishedExcerpt": ".published_excerpt", + "PublishedExcerptPublicSourceClass": ".published_excerpt_public_source_class", + "Recommendation": ".recommendation", + "RecommendationEnrichmentItem": ".recommendation_enrichment_item", + "RecommendationListResponse": ".recommendation_list_response", + "RecommendationListResponseEntity": ".recommendation_list_response_entity", + "RecommendationMedia": ".recommendation_media", + "RecommendationMediaSourceChannel": ".recommendation_media_source_channel", + "ResolveCandidate": ".resolve_candidate", + "ResolveCandidateMatch": ".resolve_candidate_match", + "ResolveSuggestion": ".resolve_suggestion", + "ResolveSuggestionMatch": ".resolve_suggestion_match", + "ResolveSuggestionReason": ".resolve_suggestion_reason", + "SearchRequestType": ".search_request_type", + "SearchResolveResponse": ".search_resolve_response", + "SearchResolveResponseEntity": ".search_resolve_response_entity", + "SignupSentResponse": ".signup_sent_response", + "SignupSentResponseNext": ".signup_sent_response_next", + "SignupSentResponseNextMethod": ".signup_sent_response_next_method", + "SignupVerifiedResponse": ".signup_verified_response", + "SpeakerIdentificationSubmittedResponse": ".speaker_identification_submitted_response", + "SpeakerIdentificationSubmittedResponseIdentification": ".speaker_identification_submitted_response_identification", + "SpeakerIdentificationSubmittedResponseIdentificationEntity": ".speaker_identification_submitted_response_identification_entity", + "SpeakerIdentificationSubmittedResponseIdentificationStatus": ".speaker_identification_submitted_response_identification_status", + "StaleMetadataChange": ".stale_metadata_change", + "TeamMember": ".team_member", + "TeamMemberRole": ".team_member_role", + "TeamMemberSeatType": ".team_member_seat_type", + "TeamMemberSpend": ".team_member_spend", + "TeamMemberSpendRole": ".team_member_spend_role", + "TeamMemberSpendSeatType": ".team_member_spend_seat_type", + "TeamMembersResponse": ".team_members_response", + "TeamMembersResponseTeam": ".team_members_response_team", + "TeamSpendResponse": ".team_spend_response", + "TeamUsageEvent": ".team_usage_event", + "TeamUsageEventsResponse": ".team_usage_events_response", + "TopicPageResponse": ".topic_page_response", + "TopicPageResponseChannelsItem": ".topic_page_response_channels_item", + "TopicPageResponseChannelsItemSentiment": ".topic_page_response_channels_item_sentiment", + "TopicPageResponseCompaniesItem": ".topic_page_response_companies_item", + "TopicPageResponseCompaniesItemSentiment": ".topic_page_response_companies_item_sentiment", + "TopicPageResponseEntity": ".topic_page_response_entity", + "TopicPageResponseEntityType": ".topic_page_response_entity_type", + "TopicPageResponseMentionsByMonthItem": ".topic_page_response_mentions_by_month_item", + "TopicPageResponseProductsItem": ".topic_page_response_products_item", + "TopicPageResponseProductsItemSentiment": ".topic_page_response_products_item_sentiment", + "TopicPageResponseRelatedTopicsItem": ".topic_page_response_related_topics_item", + "TopicPageResponseRelatedTopicsItemSentiment": ".topic_page_response_related_topics_item_sentiment", + "TopicPageResponseStats": ".topic_page_response_stats", + "TopicPageResponseVoicesItem": ".topic_page_response_voices_item", + "TopicPageResponseVoicesItemRole": ".topic_page_response_voices_item_role", + "TopicPageResponseVoicesItemSentiment": ".topic_page_response_voices_item_sentiment", + "Tracker": ".tracker", + "TrackerListResponse": ".tracker_list_response", + "TrackerMutationResponse": ".tracker_mutation_response", + "TranscriptEditSubmittedResponse": ".transcript_edit_submitted_response", + "TranscriptEditSubmittedResponseEdit": ".transcript_edit_submitted_response_edit", + "TranscriptEditSubmittedResponseEditStatus": ".transcript_edit_submitted_response_edit_status", + "TranscriptPending": ".transcript_pending", + "TranscriptPendingPremiumJob": ".transcript_pending_premium_job", + "TranscriptPendingQuality": ".transcript_pending_quality", + "TranscriptPurchaseQuote": ".transcript_purchase_quote", + "TranscriptPurchaseQuoteBillingScope": ".transcript_purchase_quote_billing_scope", + "TranscriptPurchaseQuoteCharge": ".transcript_purchase_quote_charge", + "TranscriptPurchaseQuoteChargeUnit": ".transcript_purchase_quote_charge_unit", + "TranscriptQuote": ".transcript_quote", + "TranscriptResponse": ".transcript_response", + "TranscriptResponseAccess": ".transcript_response_access", + "TranscriptResponseAccessGate": ".transcript_response_access_gate", + "TranscriptResponseAccessReason": ".transcript_response_access_reason", + "TranscriptResponseAccessType": ".transcript_response_access_type", + "TranscriptResponseAccessUnlock": ".transcript_response_access_unlock", + "TranscriptResponseAccessUnlockAction": ".transcript_response_access_unlock_action", + "TranscriptResponseLinesItem": ".transcript_response_lines_item", + "TranscriptResponseParagraphsItem": ".transcript_response_paragraphs_item", + "TranscriptResponsePremiumJob": ".transcript_response_premium_job", + "TranscriptResponseQuality": ".transcript_response_quality", + "TranscriptResponseRange": ".transcript_response_range", + "TranscriptResponseSource": ".transcript_response_source", + "TranscriptResponseSpeakersItem": ".transcript_response_speakers_item", + "TranscriptResult": ".transcript_result", + "TranscriptResult_Pending": ".transcript_result", + "TranscriptResult_Ready": ".transcript_result", + "TranscriptSearchChunk": ".transcript_search_chunk", + "TranscriptSearchResponse": ".transcript_search_response", + "TranscriptSearchResponseAccess": ".transcript_search_response_access", + "TranscriptSearchResponseAccessGate": ".transcript_search_response_access_gate", + "TranscriptSearchResponseAccessReason": ".transcript_search_response_access_reason", + "TranscriptSearchResponseAccessType": ".transcript_search_response_access_type", + "TranscriptSearchResponseAccessUnlock": ".transcript_search_response_access_unlock", + "TranscriptSearchResponseAccessUnlockAction": ".transcript_search_response_access_unlock_action", + "TranscriptSearchResponseFilters": ".transcript_search_response_filters", + "TranscriptSearchResponseSearchIndex": ".transcript_search_response_search_index", + "TranscriptSearchResponseSearchIndexState": ".transcript_search_response_search_index_state", + "TranscriptSettings": ".transcript_settings", + "TranscriptSettingsQuality": ".transcript_settings_quality", + "TranscriptVideo": ".transcript_video", + "TranscriptionListResponse": ".transcription_list_response", + "TranscriptionListResponseRequestsItem": ".transcription_list_response_requests_item", + "TranscriptionRequest": ".transcription_request", + "TranscriptionRequestCharge": ".transcription_request_charge", + "TranscriptionRequestChargeUnit": ".transcription_request_charge_unit", + "TranscriptionRequestQuote": ".transcription_request_quote", + "TranscriptionRequestStage": ".transcription_request_stage", + "TranscriptionRequestState": ".transcription_request_state", + "TranscriptionRequestStatus": ".transcription_request_status", + "TranscriptionSubmitResponse": ".transcription_submit_response", + "VideoCaptionsResponse": ".video_captions_response", + "VideoMergeListResponse": ".video_merge_list_response", + "VideoMergeListResponseMergesItem": ".video_merge_list_response_merges_item", + "VideoMergeListResponseMergesItemStatus": ".video_merge_list_response_merges_item_status", + "VideoMergeSubmittedResponse": ".video_merge_submitted_response", + "VideoMergeSubmittedResponseMerge": ".video_merge_submitted_response_merge", + "VideoMergeSubmittedResponseMergeStatus": ".video_merge_submitted_response_merge_status", + "WebhookSecretRotateResponse": ".webhook_secret_rotate_response", + "WithdrawnResponse": ".withdrawn_response", + "WrongClassificationChange": ".wrong_classification_change", + "WrongClassificationChangeMentionClass": ".wrong_classification_change_mention_class", + "WrongEntityChange": ".wrong_entity_change", + "WrongEntityTypeChange": ".wrong_entity_type_change", + "WrongEntityTypeChangeField": ".wrong_entity_type_change_field", +} + + +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__ = [ + "AccountSettings", + "Alert", + "AlertEvidenceKind", + "AlertListResponse", + "AlertMonitor", + "AlertTracker", + "BadRankingChange", + "CaptionTrack", + "ChannelCoverageResponse", + "ChannelCoverageResponseChannel", + "ChannelCoverageResponseChannelSourceMix", + "ChannelGuestListResponse", + "ChannelGuestListResponseExportCapabilities", + "ChannelGuestListResponseItemsItem", + "ChannelGuestListResponseItemsItemSentiment", + "ChannelPageResponse", + "ChannelPageResponseChannelInfo", + "ChannelPageResponseEntity", + "ChannelPageResponseEntityOwner", + "ChannelPageResponseEntityType", + "ChannelPageResponseEpisodesByMonthItem", + "ChannelPageResponseEpisodesItem", + "ChannelPageResponseEpisodesItemPlatform", + "ChannelPageResponseEpisodesItemSentiment", + "ChannelPageResponseEpisodesItemTimestamp", + "ChannelPageResponseEpisodesItemType", + "ChannelPageResponseGuestsItem", + "ChannelPageResponseGuestsItemRole", + "ChannelPageResponseGuestsItemSentiment", + "ChannelPageResponseHostsDetailedItem", + "ChannelPageResponseHostsDetailedItemSentiment", + "ChannelPageResponseOrganizationsItem", + "ChannelPageResponseOrganizationsItemSentiment", + "ChannelPageResponseProductsItem", + "ChannelPageResponseProductsItemSentiment", + "ChannelPageResponseRecommendationsSummary", + "ChannelPageResponseStats", + "ChannelPageResponseTopicsItem", + "ChannelPageResponseTopicsItemSentiment", + "ChannelSponsor", + "ChannelSponsorEntity", + "ChannelSponsorSponsorStatus", + "ChannelSponsorsResponse", + "ChannelSponsorsResponseAccess", + "ChannelSponsorsResponseAccessGate", + "ChannelSponsorsResponseAccessReason", + "ChannelSponsorsResponseAccessType", + "ChannelSponsorsResponseAccessUnlock", + "ChannelSponsorsResponseAccessUnlockAction", + "ChannelSponsorsResponseChannel", + "ChannelSponsorsResponseMeta", + "ChannelVideosResponse", + "ChannelVideosResponseChannel", + "ChannelVideosResponseEpisodesItem", + "CorrectionAcceptedResponse", + "CorrectionAcceptedResponseKind", + "CorrectionSeqMismatchResponse", + "DeliveryIssueChange", + "DeliveryIssueChangeChannel", + "Entity", + "EntityCard", + "EntityCardsResponse", + "EntityChannelListResponse", + "EntityChannelListResponseExportCapabilities", + "EntityChannelListResponseItemsItem", + "EntityDetailRecommendationsSummary", + "EntityDetailResponse", + "EntityLookupResponse", + "EntityMomentumResponse", + "EntityMomentumResponseAccess", + "EntityMomentumResponseAccessGate", + "EntityMomentumResponseAccessReason", + "EntityMomentumResponseAccessType", + "EntityMomentumResponseAccessUnlock", + "EntityMomentumResponseAccessUnlockAction", + "EntityMomentumResponsePaidVsOrganic", + "EntityMomentumResponseTopShowsItem", + "EntityMomentumResponseVerdict", + "EntityMomentumResponseVolume", + "EntityOrganizationListResponse", + "EntityOrganizationListResponseExportCapabilities", + "EntityOrganizationListResponseItemsItem", + "EntityOrganizationListResponseItemsItemSentiment", + "EntityPageMention", + "EntityPageMentionExcerpt", + "EntityPageMentionExcerptPublicSourceClass", + "EntityPageMentionPlatform", + "EntityPageMentionSentiment", + "EntityPageMentionType", + "EntityPeopleListResponse", + "EntityPeopleListResponseExportCapabilities", + "EntityPeopleListResponseItemsItem", + "EntityPeopleListResponseItemsItemSentiment", + "EntityPeopleListResponsePeopleMode", + "EntityProductListResponse", + "EntityProductListResponseExportCapabilities", + "EntityProductListResponseItemsItem", + "EntityProductListResponseItemsItemSentiment", + "EntityRef", + "EntityResolveResponse", + "EntityResolveResponseAsk", + "EntityResolveResponseAskOptionsItem", + "EntityResolveResponseConfidence", + "EntitySearchResponse", + "EntitySearchResult", + "EntitySearchResultRecommendationsSummary", + "EntityTopicListResponse", + "EntityTopicListResponseExportCapabilities", + "EntityTopicListResponseItemsItem", + "EntityTopicListResponseItemsItemSentiment", + "Error", + "ErrorError", + "ErrorErrorGate", + "ErrorErrorReason", + "ErrorErrorType", + "ErrorErrorUnlock", + "ErrorErrorUnlockAction", + "ExposureMeta", + "ExposureMetaAccess", + "ExposureMetaAccessChart", + "ExposureMetaAccessCls", + "ExposureMetaAccessFreshness", + "ExposureMetaAccessLadder", + "ExposureMetaAccessRows", + "ExposureMetaAccessRowsEntities", + "ExposureMetaAccessRowsMedia", + "ExposureMetaAccessRowsTopics", + "ExposureMetaAccessUnlock", + "ExposureMetaAccessUnlockLimitAction", + "ExposureMetaAccessUnlockSrc", + "ExposureMetaAccessView", + "ExposureMetaAccessWithheldItem", + "ExposureMetaAccessWithheldItemKind", + "ExposureMetaAccessWithheldItemParam", + "ExposureMetaAccessWithheldItemSection", + "ExposureMetaAccessWithheldItemWhat", + "ExposureMetaCredits", + "ExposureMetaCreditsOnDemand", + "ExposureMetaCreditsPlan", + "ExposureMetaFreeLimit", + "ExposureMetaLimitAction", + "ExposureMetaLimits", + "ExposureMetaRecentPreview", + "ExposureMetaRecentPreviewExperiment", + "ExposureMetaRecentPreviewMentions", + "ExposureMetaRecentPreviewMentionsExperiment", + "ExposureMetaRecentPreviewMentionsSubject", + "ExposureMetaRecentPreviewMentionsTeaserItemsItem", + "ExposureMetaRecentPreviewSubject", + "ExposureMetaRecentPreviewTeaserItemsItem", + "ExposureMetaTotals", + "ExposureMetaUsageLimitType", + "FeedbackCorrectionResult", + "FeedbackCorrectionResultRecommendation", + "FeedbackCorrectionResultRecommendationMedia", + "FeedbackCorrectionResultRecommendationMediaSourceChannel", + "FeedbackCorrectionResultStatus", + "FeedbackReadbackCorrection", + "FeedbackReadbackCorrectionStatus", + "FeedbackReadbackResponse", + "FeedbackReadbackResponseStatus", + "FeedbackResponse", + "FreeformSuggestedChange", + "HealthResponse", + "HealthResponseStatus", + "HealthResponseVersion", + "MeResponse", + "MeResponseCredentialKind", + "MeResponseUsage", + "MeResponseUsageCredits", + "MeResponseUsageCreditsOnDemand", + "MeResponseUsageCreditsPlan", + "MeResponseUsageHits", + "MeSettingsResponse", + "Mention", + "MentionCountsResponse", + "MentionCountsResponseMode", + "MentionCountsResponseRowsItem", + "MentionCountsResponseSharedItem", + "MentionCountsResponseSharedItemByChannelItem", + "MentionListResponse", + "MentionListResponseEntity", + "MentionListResponseUnlock", + "MentionMedia", + "MentionMediaSourceChannel", + "MentionRecommendations", + "MentionSentiment", + "MergeSuggestionChange", + "MessageResponse", + "MissedAlertChange", + "MissingResultChange", + "Monitor", + "MonitorAddTrackersResponse", + "MonitorDeleteResponse", + "MonitorEmailRecipientsItem", + "MonitorEmailRecipientsItemInvitationStatus", + "MonitorEmailRecipientsItemStatus", + "MonitorListResponse", + "MonitorListResponseMonitorsItem", + "MonitorListResponseMonitorsItemSlackIntegration", + "MonitorMutationResponse", + "MonitorMutationResponseMonitor", + "MonitorTrackersResponse", + "MonitorTrackersResponseTrackersItem", + "NamedEntityRef", + "OpenApiDocument", + "OpenApiDocumentInfo", + "OpenApiDocumentInfoContact", + "OpenApiDocumentServersItem", + "OrganizationPageResponse", + "OrganizationPageResponseChannelsItem", + "OrganizationPageResponseChannelsItemSentiment", + "OrganizationPageResponseEntity", + "OrganizationPageResponseEntityOwnedChannelsItem", + "OrganizationPageResponseEntityOwnedProductsItem", + "OrganizationPageResponseEntityType", + "OrganizationPageResponseMentionsByMonthItem", + "OrganizationPageResponsePeopleItem", + "OrganizationPageResponsePeopleItemSentiment", + "OrganizationPageResponseProductsItem", + "OrganizationPageResponseProductsItemSentiment", + "OrganizationPageResponseRoleEdge", + "OrganizationPageResponseRoleEdgeLabel", + "OrganizationPageResponseRoleEdgePeopleItem", + "OrganizationPageResponseRoleEdgeRecentAppearancesItem", + "OrganizationPageResponseRoleEdgeRole", + "OrganizationPageResponseStats", + "OrganizationPageResponseTopicsItem", + "OrganizationPageResponseTopicsItemSentiment", + "PersonAppearanceListResponse", + "PersonAppearanceListResponseItemsItem", + "PersonAppearanceListResponseItemsItemPlatform", + "PersonAppearanceListResponseItemsItemSentiment", + "PersonAppearanceListResponseItemsItemType", + "PersonPageResponse", + "PersonPageResponseAppearancesByMonthItem", + "PersonPageResponseAppearancesItem", + "PersonPageResponseAppearancesItemPlatform", + "PersonPageResponseAppearancesItemSentiment", + "PersonPageResponseAppearancesItemType", + "PersonPageResponseBrandsItem", + "PersonPageResponseBrandsItemSentiment", + "PersonPageResponseEntity", + "PersonPageResponseEntityOwnedChannelsItem", + "PersonPageResponseEntityOwnedProductsItem", + "PersonPageResponseMentionsByMonthItem", + "PersonPageResponseMentionsItem", + "PersonPageResponseMentionsItemPlatform", + "PersonPageResponseMentionsItemSentiment", + "PersonPageResponseMentionsItemType", + "PersonPageResponsePeopleItem", + "PersonPageResponsePeopleItemRole", + "PersonPageResponsePeopleItemSentiment", + "PersonPageResponseProductsItem", + "PersonPageResponseProductsItemSentiment", + "PersonPageResponseRoleEdge", + "PersonPageResponseRoleEdgeLabel", + "PersonPageResponseRoleEdgeRole", + "PersonPageResponseStats", + "PersonPageResponseTopicsItem", + "PersonPageResponseTopicsItemSentiment", + "ProductPageResponse", + "ProductPageResponseChannelsItem", + "ProductPageResponseChannelsItemSentiment", + "ProductPageResponseEntity", + "ProductPageResponseEntityOwner", + "ProductPageResponseEntityParentOrg", + "ProductPageResponseEntityType", + "ProductPageResponseMentionsByMonthItem", + "ProductPageResponseOpportunities", + "ProductPageResponseOrganizationsItem", + "ProductPageResponseOrganizationsItemSentiment", + "ProductPageResponsePeopleItem", + "ProductPageResponsePeopleItemSentiment", + "ProductPageResponseStats", + "ProductPageResponseTopicsItem", + "ProductPageResponseTopicsItemSentiment", + "PublishedExcerpt", + "PublishedExcerptPublicSourceClass", + "Recommendation", + "RecommendationEnrichmentItem", + "RecommendationListResponse", + "RecommendationListResponseEntity", + "RecommendationMedia", + "RecommendationMediaSourceChannel", + "ResolveCandidate", + "ResolveCandidateMatch", + "ResolveSuggestion", + "ResolveSuggestionMatch", + "ResolveSuggestionReason", + "SearchRequestType", + "SearchResolveResponse", + "SearchResolveResponseEntity", + "SignupSentResponse", + "SignupSentResponseNext", + "SignupSentResponseNextMethod", + "SignupVerifiedResponse", + "SpeakerIdentificationSubmittedResponse", + "SpeakerIdentificationSubmittedResponseIdentification", + "SpeakerIdentificationSubmittedResponseIdentificationEntity", + "SpeakerIdentificationSubmittedResponseIdentificationStatus", + "StaleMetadataChange", + "TeamMember", + "TeamMemberRole", + "TeamMemberSeatType", + "TeamMemberSpend", + "TeamMemberSpendRole", + "TeamMemberSpendSeatType", + "TeamMembersResponse", + "TeamMembersResponseTeam", + "TeamSpendResponse", + "TeamUsageEvent", + "TeamUsageEventsResponse", + "TopicPageResponse", + "TopicPageResponseChannelsItem", + "TopicPageResponseChannelsItemSentiment", + "TopicPageResponseCompaniesItem", + "TopicPageResponseCompaniesItemSentiment", + "TopicPageResponseEntity", + "TopicPageResponseEntityType", + "TopicPageResponseMentionsByMonthItem", + "TopicPageResponseProductsItem", + "TopicPageResponseProductsItemSentiment", + "TopicPageResponseRelatedTopicsItem", + "TopicPageResponseRelatedTopicsItemSentiment", + "TopicPageResponseStats", + "TopicPageResponseVoicesItem", + "TopicPageResponseVoicesItemRole", + "TopicPageResponseVoicesItemSentiment", + "Tracker", + "TrackerListResponse", + "TrackerMutationResponse", + "TranscriptEditSubmittedResponse", + "TranscriptEditSubmittedResponseEdit", + "TranscriptEditSubmittedResponseEditStatus", + "TranscriptPending", + "TranscriptPendingPremiumJob", + "TranscriptPendingQuality", + "TranscriptPurchaseQuote", + "TranscriptPurchaseQuoteBillingScope", + "TranscriptPurchaseQuoteCharge", + "TranscriptPurchaseQuoteChargeUnit", + "TranscriptQuote", + "TranscriptResponse", + "TranscriptResponseAccess", + "TranscriptResponseAccessGate", + "TranscriptResponseAccessReason", + "TranscriptResponseAccessType", + "TranscriptResponseAccessUnlock", + "TranscriptResponseAccessUnlockAction", + "TranscriptResponseLinesItem", + "TranscriptResponseParagraphsItem", + "TranscriptResponsePremiumJob", + "TranscriptResponseQuality", + "TranscriptResponseRange", + "TranscriptResponseSource", + "TranscriptResponseSpeakersItem", + "TranscriptResult", + "TranscriptResult_Pending", + "TranscriptResult_Ready", + "TranscriptSearchChunk", + "TranscriptSearchResponse", + "TranscriptSearchResponseAccess", + "TranscriptSearchResponseAccessGate", + "TranscriptSearchResponseAccessReason", + "TranscriptSearchResponseAccessType", + "TranscriptSearchResponseAccessUnlock", + "TranscriptSearchResponseAccessUnlockAction", + "TranscriptSearchResponseFilters", + "TranscriptSearchResponseSearchIndex", + "TranscriptSearchResponseSearchIndexState", + "TranscriptSettings", + "TranscriptSettingsQuality", + "TranscriptVideo", + "TranscriptionListResponse", + "TranscriptionListResponseRequestsItem", + "TranscriptionRequest", + "TranscriptionRequestCharge", + "TranscriptionRequestChargeUnit", + "TranscriptionRequestQuote", + "TranscriptionRequestStage", + "TranscriptionRequestState", + "TranscriptionRequestStatus", + "TranscriptionSubmitResponse", + "VideoCaptionsResponse", + "VideoMergeListResponse", + "VideoMergeListResponseMergesItem", + "VideoMergeListResponseMergesItemStatus", + "VideoMergeSubmittedResponse", + "VideoMergeSubmittedResponseMerge", + "VideoMergeSubmittedResponseMergeStatus", + "WebhookSecretRotateResponse", + "WithdrawnResponse", + "WrongClassificationChange", + "WrongClassificationChangeMentionClass", + "WrongEntityChange", + "WrongEntityTypeChange", + "WrongEntityTypeChangeField", +] diff --git a/src/arcmira/types/account_settings.py b/src/arcmira/types/account_settings.py new file mode 100644 index 0000000..d42ac5c --- /dev/null +++ b/src/arcmira/types/account_settings.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 .transcript_settings import TranscriptSettings + + +class AccountSettings(UniversalBaseModel): + """ + Account defaults every key of this account resolves against. Set them with PATCH /v1/me/settings. + """ + + transcripts: TranscriptSettings + + 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/arcmira/types/alert.py b/src/arcmira/types/alert.py new file mode 100644 index 0000000..ae50946 --- /dev/null +++ b/src/arcmira/types/alert.py @@ -0,0 +1,110 @@ +# 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 .alert_evidence_kind import AlertEvidenceKind +from .alert_monitor import AlertMonitor +from .alert_tracker import AlertTracker + + +class Alert(UniversalBaseModel): + id: str = pydantic.Field() + """ + Alert delivery id. + """ + + tracker_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Id of the tracker (tracked entity) that produced the alert. + """ + + monitor_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Id of the monitor the tracker belongs to. Null for trackers outside a monitor. + """ + + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("ent_{n}") of the entity that triggered the alert, when recorded. Null on older rows that were written before entity_id was stored on alert_delivery. Resolve the entity through mention_id or the embedded tracker when this is null. + """ + + mention_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("men_{n}") of the mention/appearance row that triggered the alert. Joins directly against mention rows (e.g. /v1/mentions). Null when not appearance-scoped. + """ + + media_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Media row that triggered the alert. A raw integer database id, matching the numeric media ids used elsewhere in the API (e.g. mention media.id). Null when not media-scoped. + """ + + appearance_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Appearance row that triggered the alert. A raw integer database id, matching the numeric appearance_id on mention rows (same number as in mention_id). Null when not appearance-scoped. + """ + + excerpt_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Id of the active mention excerpt used as mention evidence. Null when the alert was sent before evidence was recorded. + """ + + evidence_kind: typing.Optional[AlertEvidenceKind] = pydantic.Field(default=None) + """ + Which evidence layer was sent. Null on older rows. + """ + + channel: str = pydantic.Field() + """ + Delivery channel. Values: email (sent by email), webhook (POSTed to the configured webhook URL), slack (sent to Slack). + """ + + status: str = pydantic.Field() + """ + Delivery status. Values: pending (queued for delivery), sent (delivered), failed (delivery failed; see error_message), skipped (delivery intentionally skipped). + """ + + final_status: typing.Optional[str] = pydantic.Field(default=None) + """ + Terminal status after retries. Null while delivery is still in progress. + """ + + error_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Error details for failed deliveries. Null unless delivery failed. + """ + + scheduled_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When delivery was scheduled. Null when delivered immediately. + """ + + sent_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the alert was actually sent. Null until delivery succeeds. + """ + + created_at: str = pydantic.Field() + """ + When the alert row was created. + """ + + tracker: AlertTracker = pydantic.Field() + """ + The tracker the alert belongs to. Fields are null when the tracker row was deleted. + """ + + monitor: typing.Optional[AlertMonitor] = pydantic.Field(default=None) + """ + The monitor the tracker belongs to. Null for trackers outside a monitor. + """ + + 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/arcmira/types/alert_evidence_kind.py b/src/arcmira/types/alert_evidence_kind.py new file mode 100644 index 0000000..831c048 --- /dev/null +++ b/src/arcmira/types/alert_evidence_kind.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +AlertEvidenceKind = typing.Union[typing.Literal["excerpt"], typing.Any] diff --git a/src/arcmira/types/alert_list_response.py b/src/arcmira/types/alert_list_response.py new file mode 100644 index 0000000..46bd150 --- /dev/null +++ b/src/arcmira/types/alert_list_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 .alert import Alert + + +class AlertListResponse(UniversalBaseModel): + data: typing.List[Alert] = pydantic.Field() + """ + Newest alerts first. + """ + + has_more: bool = pydantic.Field() + """ + CURRENTLY always false: this endpoint returns the newest n alerts as a single page and does not paginate. + """ + + next_cursor: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Always null: this endpoint does not paginate. + """ + + 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/arcmira/types/alert_monitor.py b/src/arcmira/types/alert_monitor.py new file mode 100644 index 0000000..244363b --- /dev/null +++ b/src/arcmira/types/alert_monitor.py @@ -0,0 +1,31 @@ +# 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 AlertMonitor(UniversalBaseModel): + """ + The monitor the tracker belongs to. Null for trackers outside a monitor. + """ + + id: str = pydantic.Field() + """ + Monitor id. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Monitor 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/arcmira/types/alert_tracker.py b/src/arcmira/types/alert_tracker.py new file mode 100644 index 0000000..777f6ac --- /dev/null +++ b/src/arcmira/types/alert_tracker.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 + + +class AlertTracker(UniversalBaseModel): + """ + The tracker the alert belongs to. Fields are null when the tracker row was deleted. + """ + + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Tracker id. + """ + + entity_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Tracked entity name. + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Tracked entity type. + """ + + display_name: typing.Optional[str] = pydantic.Field(default=None) + """ + User-facing display name for the tracker. Null when not customized. + """ + + 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/arcmira/types/bad_ranking_change.py b/src/arcmira/types/bad_ranking_change.py new file mode 100644 index 0000000..6befc87 --- /dev/null +++ b/src/arcmira/types/bad_ranking_change.py @@ -0,0 +1,31 @@ +# 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 BadRankingChange(UniversalBaseModel): + """ + For issue_type bad_ranking: the expected and observed positions of the row. + """ + + expected_rank: typing.Optional[int] = pydantic.Field(default=None) + """ + Where the row should have ranked (1-based). + """ + + observed_rank: typing.Optional[int] = pydantic.Field(default=None) + """ + Where the row actually ranked (1-based). Most useful on search feedback. + """ + + 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/arcmira/types/caption_track.py b/src/arcmira/types/caption_track.py new file mode 100644 index 0000000..112487c --- /dev/null +++ b/src/arcmira/types/caption_track.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 CaptionTrack(UniversalBaseModel): + code: str = pydantic.Field() + """ + Caption track code, e.g. en or de. Pass it as language to select this track. + """ + + name: str = pydantic.Field() + """ + Track name as YouTube reports it. + """ + + generated: bool = pydantic.Field() + """ + True for YouTube automatic captions, false for a track the channel wrote or approved. + """ + + 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/arcmira/types/channel_coverage_response.py b/src/arcmira/types/channel_coverage_response.py new file mode 100644 index 0000000..08950d1 --- /dev/null +++ b/src/arcmira/types/channel_coverage_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 .channel_coverage_response_channel import ChannelCoverageResponseChannel + + +class ChannelCoverageResponse(UniversalBaseModel): + channel: ChannelCoverageResponseChannel + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. + """ + + 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/arcmira/types/channel_coverage_response_channel.py b/src/arcmira/types/channel_coverage_response_channel.py new file mode 100644 index 0000000..9f132cd --- /dev/null +++ b/src/arcmira/types/channel_coverage_response_channel.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 .channel_coverage_response_channel_source_mix import ChannelCoverageResponseChannelSourceMix + + +class ChannelCoverageResponseChannel(UniversalBaseModel): + youtube_channel_id: str = pydantic.Field() + """ + The channel id as supplied. + """ + + searchable_videos: int = pydantic.Field() + """ + Videos with a completed transcript. 0 means we do not cover the channel. + """ + + indexed_through: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest publish date among those videos. Mentions, entity lookups and transcripts read through here. Null when nothing is indexed. + """ + + search_indexed_through: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest publish date transcript search can hit. New transcripts are searchable when they are indexed, so it equals indexed_through. + """ + + source_mix: ChannelCoverageResponseChannelSourceMix = pydantic.Field() + """ + searchable_videos split by transcript source class. + """ + + 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/arcmira/types/channel_coverage_response_channel_source_mix.py b/src/arcmira/types/channel_coverage_response_channel_source_mix.py new file mode 100644 index 0000000..01b60f9 --- /dev/null +++ b/src/arcmira/types/channel_coverage_response_channel_source_mix.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 ChannelCoverageResponseChannelSourceMix(UniversalBaseModel): + """ + searchable_videos split by transcript source class. + """ + + arcmira_premium: int + creator_captions: int + third_party_quick: int + + 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/arcmira/types/channel_guest_list_response.py b/src/arcmira/types/channel_guest_list_response.py new file mode 100644 index 0000000..b1c8bb6 --- /dev/null +++ b/src/arcmira/types/channel_guest_list_response.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_guest_list_response_export_capabilities import ChannelGuestListResponseExportCapabilities +from .channel_guest_list_response_items_item import ChannelGuestListResponseItemsItem +from .exposure_meta import ExposureMeta + + +class ChannelGuestListResponse(UniversalBaseModel): + items: typing.List[ChannelGuestListResponseItemsItem] = pydantic.Field() + """ + People who appeared on the channel, highest count first by default. count is the episodes they appeared in. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + export_capabilities: typing_extensions.Annotated[ + ChannelGuestListResponseExportCapabilities, + FieldMetadata(alias="exportCapabilities"), + pydantic.Field( + alias="exportCapabilities", + description="Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts.", + ), + ] + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/channel_guest_list_response_export_capabilities.py b/src/arcmira/types/channel_guest_list_response_export_capabilities.py new file mode 100644 index 0000000..96819ed --- /dev/null +++ b/src/arcmira/types/channel_guest_list_response_export_capabilities.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ChannelGuestListResponseExportCapabilities(UniversalBaseModel): + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + highest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="highestCount"), + pydantic.Field( + alias="highestCount", description="Always true: the highest-count ordering is always available." + ), + ] + """ + Always true: the highest-count ordering is always available. + """ + + lowest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="lowestCount"), + pydantic.Field(alias="lowestCount", description="True when the lowest-count ordering is available."), + ] + """ + True when the lowest-count ordering is available. + """ + + alternate_sort: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="alternateSort"), + pydantic.Field(alias="alternateSort", description="True when sort keys other than count are available."), + ] + """ + True when sort keys other than count are available. + """ + + search: bool = pydantic.Field() + """ + True when the q filter is available. + """ + + available_rows: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="availableRows"), + pydantic.Field( + alias="availableRows", + description="Present only when a stored top-N is smaller than the full result set: the rows it holds.", + ), + ] = None + """ + Present only when a stored top-N is smaller than the full result set: the rows it holds. + """ + + 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/arcmira/types/channel_guest_list_response_items_item.py b/src/arcmira/types/channel_guest_list_response_items_item.py new file mode 100644 index 0000000..b59ac36 --- /dev/null +++ b/src/arcmira/types/channel_guest_list_response_items_item.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_guest_list_response_items_item_sentiment import ChannelGuestListResponseItemsItemSentiment + + +class ChannelGuestListResponseItemsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id of the person. + """ + + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: ChannelGuestListResponseItemsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/channel_guest_list_response_items_item_sentiment.py b/src/arcmira/types/channel_guest_list_response_items_item_sentiment.py new file mode 100644 index 0000000..7e59e41 --- /dev/null +++ b/src/arcmira/types/channel_guest_list_response_items_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelGuestListResponseItemsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/channel_page_response.py b/src/arcmira/types/channel_page_response.py new file mode 100644 index 0000000..0fb2382 --- /dev/null +++ b/src/arcmira/types/channel_page_response.py @@ -0,0 +1,103 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_channel_info import ChannelPageResponseChannelInfo +from .channel_page_response_entity import ChannelPageResponseEntity +from .channel_page_response_episodes_by_month_item import ChannelPageResponseEpisodesByMonthItem +from .channel_page_response_episodes_item import ChannelPageResponseEpisodesItem +from .channel_page_response_guests_item import ChannelPageResponseGuestsItem +from .channel_page_response_hosts_detailed_item import ChannelPageResponseHostsDetailedItem +from .channel_page_response_organizations_item import ChannelPageResponseOrganizationsItem +from .channel_page_response_products_item import ChannelPageResponseProductsItem +from .channel_page_response_recommendations_summary import ChannelPageResponseRecommendationsSummary +from .channel_page_response_stats import ChannelPageResponseStats +from .channel_page_response_topics_item import ChannelPageResponseTopicsItem +from .exposure_meta import ExposureMeta + + +class ChannelPageResponse(UniversalBaseModel): + entity: ChannelPageResponseEntity = pydantic.Field() + """ + The channel and who owns it. + """ + + hosts: typing.List[str] = pydantic.Field() + """ + Host names: configured hosts when the channel has them, else discovered ones. + """ + + stats: ChannelPageResponseStats = pydantic.Field() + """ + Header vitals. Open on every plan. + """ + + channel_info: typing_extensions.Annotated[ + ChannelPageResponseChannelInfo, + FieldMetadata(alias="channelInfo"), + pydantic.Field(alias="channelInfo", description="Channel details, some gated by plan."), + ] + """ + Channel details, some gated by plan. + """ + + episodes_by_month: typing_extensions.Annotated[ + typing.List[ChannelPageResponseEpisodesByMonthItem], + FieldMetadata(alias="episodesByMonth"), + pydantic.Field( + alias="episodesByMonth", + description="Videos published per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart.", + ), + ] + """ + Videos published per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart. + """ + + topics: typing.List[ChannelPageResponseTopicsItem] = pydantic.Field() + """ + Topics across the channel, highest count first. + """ + + hosts_detailed: typing.List[ChannelPageResponseHostsDetailedItem] = pydantic.Field() + """ + Hosts with their details, in the same order as hosts. + """ + + guests: typing.List[ChannelPageResponseGuestsItem] = pydantic.Field() + """ + Guests who are not hosts, most appearances first. + """ + + organizations: typing.List[ChannelPageResponseOrganizationsItem] = pydantic.Field() + """ + Organizations mentioned across the channel, most mentions first. + """ + + products: typing.List[ChannelPageResponseProductsItem] = pydantic.Field() + """ + Products mentioned across the channel, most mentions first. + """ + + episodes: typing.List[ChannelPageResponseEpisodesItem] = pydantic.Field() + """ + The channel's newest 50 indexed videos. Open on every plan. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + recommendations_summary: ChannelPageResponseRecommendationsSummary = pydantic.Field() + """ + Sponsor teaser for the channel. + """ + + 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/arcmira/types/channel_page_response_channel_info.py b/src/arcmira/types/channel_page_response_channel_info.py new file mode 100644 index 0000000..fc523d9 --- /dev/null +++ b/src/arcmira/types/channel_page_response_channel_info.py @@ -0,0 +1,72 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ChannelPageResponseChannelInfo(UniversalBaseModel): + """ + Channel details, some gated by plan. + """ + + subscriber_count: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="subscriberCount"), + pydantic.Field( + alias="subscriberCount", description="Subscriber count as YouTube displays it. Null when unknown." + ), + ] = None + """ + Subscriber count as YouTube displays it. Null when unknown. + """ + + avg_views: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="avgViews"), + pydantic.Field( + alias="avgViews", description="Average views per video as display text. Null when the plan hides it." + ), + ] = None + """ + Average views per video as display text. Null when the plan hides it. + """ + + episode_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="episodeCount"), + pydantic.Field(alias="episodeCount", description="Indexed videos. Null when the plan hides it."), + ] = None + """ + Indexed videos. Null when the plan hides it. + """ + + guest_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="guestCount"), + pydantic.Field(alias="guestCount", description="Unique guests. Null when the plan hides it."), + ] = None + """ + Unique guests. Null when the plan hides it. + """ + + start_date: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="startDate"), + pydantic.Field(alias="startDate", description="Publish date of the oldest indexed video. Null when none."), + ] = None + """ + Publish date of the oldest indexed video. Null when 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/arcmira/types/channel_page_response_entity.py b/src/arcmira/types/channel_page_response_entity.py new file mode 100644 index 0000000..38b421f --- /dev/null +++ b/src/arcmira/types/channel_page_response_entity.py @@ -0,0 +1,122 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_entity_owner import ChannelPageResponseEntityOwner +from .channel_page_response_entity_type import ChannelPageResponseEntityType + + +class ChannelPageResponseEntity(UniversalBaseModel): + """ + The channel and who owns it. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Channel name. + """ + + display_name: str = pydantic.Field() + """ + Label that tells the channel apart from an owner of the same name, e.g. "AdQuick (channel)". Equals name otherwise. + """ + + type: ChannelPageResponseEntityType = pydantic.Field() + """ + Always channel. + """ + + category: str = pydantic.Field() + """ + The stored platform, or YouTube when none is stored. + """ + + platform: str = pydantic.Field() + """ + The stored platform, or youtube when none is stored. + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field(alias="channelId", description="YouTube channel id (UC form). Null when unknown."), + ] = None + """ + YouTube channel id (UC form). Null when unknown. + """ + + youtube_channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="youtubeChannelId"), + pydantic.Field(alias="youtubeChannelId", description="Same value as channelId."), + ] = None + """ + Same value as channelId. + """ + + youtube_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="youtubeHandle"), + pydantic.Field(alias="youtubeHandle", description="YouTube @handle. Null when unknown."), + ] = None + """ + YouTube @handle. Null when unknown. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Channel URL. Null when unknown. + """ + + avatar_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="avatarUrl"), + pydantic.Field(alias="avatarUrl", description="Channel avatar URL. Null when unknown."), + ] = None + """ + Channel avatar URL. Null when unknown. + """ + + avatar_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="avatarCheckedAt"), + pydantic.Field( + alias="avatarCheckedAt", + description="When the image pipeline last checked the stored avatar. Null when the avatar came from channel metadata instead.", + ), + ] = None + """ + When the image pipeline last checked the stored avatar. Null when the avatar came from channel metadata instead. + """ + + banner_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="bannerUrl"), + pydantic.Field(alias="bannerUrl", description="Channel banner URL. Null when unknown."), + ] = None + """ + Channel banner URL. Null when unknown. + """ + + owner: typing.Optional[ChannelPageResponseEntityOwner] = pydantic.Field(default=None) + """ + The organization or person that owns this entity. Null when no owner is recorded. + """ + + 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/arcmira/types/channel_page_response_entity_owner.py b/src/arcmira/types/channel_page_response_entity_owner.py new file mode 100644 index 0000000..fc610f8 --- /dev/null +++ b/src/arcmira/types/channel_page_response_entity_owner.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 + + +class ChannelPageResponseEntityOwner(UniversalBaseModel): + """ + The organization or person that owns this entity. Null when no owner is recorded. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id of the owner. + """ + + name: str = pydantic.Field() + """ + Owner name. + """ + + type: str = pydantic.Field() + """ + Owner entity type: organization (legacy rows may read company or brand) or person. + """ + + route: str = pydantic.Field() + """ + Site-relative route of the owner page on arcmira.com. + """ + + 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/arcmira/types/channel_page_response_entity_type.py b/src/arcmira/types/channel_page_response_entity_type.py new file mode 100644 index 0000000..14f3ab8 --- /dev/null +++ b/src/arcmira/types/channel_page_response_entity_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseEntityType = typing.Union[typing.Literal["channel"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_episodes_by_month_item.py b/src/arcmira/types/channel_page_response_episodes_by_month_item.py new file mode 100644 index 0000000..7d38def --- /dev/null +++ b/src/arcmira/types/channel_page_response_episodes_by_month_item.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ChannelPageResponseEpisodesByMonthItem(UniversalBaseModel): + month: str = pydantic.Field() + """ + Three-letter month name, e.g. Jan. + """ + + year_month: typing_extensions.Annotated[ + str, FieldMetadata(alias="yearMonth"), pydantic.Field(alias="yearMonth", description="The month as YYYY-MM.") + ] + """ + The month as YYYY-MM. + """ + + count: int = pydantic.Field() + """ + Media that month. Months inside the withheld window read 0. + """ + + 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/arcmira/types/channel_page_response_episodes_item.py b/src/arcmira/types/channel_page_response_episodes_item.py new file mode 100644 index 0000000..d02107a --- /dev/null +++ b/src/arcmira/types/channel_page_response_episodes_item.py @@ -0,0 +1,130 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_episodes_item_platform import ChannelPageResponseEpisodesItemPlatform +from .channel_page_response_episodes_item_sentiment import ChannelPageResponseEpisodesItemSentiment +from .channel_page_response_episodes_item_timestamp import ChannelPageResponseEpisodesItemTimestamp +from .channel_page_response_episodes_item_type import ChannelPageResponseEpisodesItemType + + +class ChannelPageResponseEpisodesItem(UniversalBaseModel): + id: str = pydantic.Field() + """ + Raw media row id, as a string. + """ + + date: str = pydantic.Field() + """ + Publish date as locale display text, or Unknown. + """ + + published_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish timestamp. Null when unknown."), + ] = None + """ + Publish timestamp. Null when unknown. + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + channel: str = pydantic.Field() + """ + The channel name. + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field( + alias="channelId", description="YouTube channel id of the video, else the channel's. Null when unknown." + ), + ] = None + """ + YouTube channel id of the video, else the channel's. Null when unknown. + """ + + channel_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelHandle"), + pydantic.Field(alias="channelHandle", description="The channel's YouTube handle. Null when unknown."), + ] = None + """ + The channel's YouTube handle. Null when unknown. + """ + + platform: ChannelPageResponseEpisodesItemPlatform = pydantic.Field() + """ + Always youtube. + """ + + thumbnail: str = pydantic.Field() + """ + Video thumbnail URL. + """ + + thumbnail_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="thumbnailUrl"), + pydantic.Field(alias="thumbnailUrl", description="Video thumbnail URL. Same value as thumbnail."), + ] + """ + Video thumbnail URL. Same value as thumbnail. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + duration: typing.Optional[float] = pydantic.Field(default=None) + """ + Video length in seconds. Null when unknown. + """ + + type: ChannelPageResponseEpisodesItemType = pydantic.Field() + """ + interview when a guest appeared, solo otherwise. + """ + + context: str = pydantic.Field() + """ + "Interview with {guest}" or "Episode". + """ + + sentiment: ChannelPageResponseEpisodesItemSentiment = pydantic.Field() + """ + Always neutral. + """ + + timestamp: ChannelPageResponseEpisodesItemTimestamp = pydantic.Field() + """ + Always 00:00. + """ + + guest: typing.Optional[str] = pydantic.Field(default=None) + """ + One guest who appeared in the video. Null when 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/arcmira/types/channel_page_response_episodes_item_platform.py b/src/arcmira/types/channel_page_response_episodes_item_platform.py new file mode 100644 index 0000000..ca3d6d3 --- /dev/null +++ b/src/arcmira/types/channel_page_response_episodes_item_platform.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseEpisodesItemPlatform = typing.Union[typing.Literal["youtube"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_episodes_item_sentiment.py b/src/arcmira/types/channel_page_response_episodes_item_sentiment.py new file mode 100644 index 0000000..27a850f --- /dev/null +++ b/src/arcmira/types/channel_page_response_episodes_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseEpisodesItemSentiment = typing.Union[typing.Literal["neutral"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_episodes_item_timestamp.py b/src/arcmira/types/channel_page_response_episodes_item_timestamp.py new file mode 100644 index 0000000..978a840 --- /dev/null +++ b/src/arcmira/types/channel_page_response_episodes_item_timestamp.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseEpisodesItemTimestamp = typing.Union[typing.Literal["00:00"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_episodes_item_type.py b/src/arcmira/types/channel_page_response_episodes_item_type.py new file mode 100644 index 0000000..2af698c --- /dev/null +++ b/src/arcmira/types/channel_page_response_episodes_item_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseEpisodesItemType = typing.Union[typing.Literal["interview", "solo"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_guests_item.py b/src/arcmira/types/channel_page_response_guests_item.py new file mode 100644 index 0000000..1e504b6 --- /dev/null +++ b/src/arcmira/types/channel_page_response_guests_item.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_guests_item_role import ChannelPageResponseGuestsItemRole +from .channel_page_response_guests_item_sentiment import ChannelPageResponseGuestsItemSentiment + + +class ChannelPageResponseGuestsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Videos on the channel the guest appeared in. Null when the plan hides counts. + """ + + sentiment: ChannelPageResponseGuestsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + role: ChannelPageResponseGuestsItemRole = pydantic.Field() + """ + Always Guest. + """ + + 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/arcmira/types/channel_page_response_guests_item_role.py b/src/arcmira/types/channel_page_response_guests_item_role.py new file mode 100644 index 0000000..45d3b44 --- /dev/null +++ b/src/arcmira/types/channel_page_response_guests_item_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseGuestsItemRole = typing.Union[typing.Literal["Guest"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_guests_item_sentiment.py b/src/arcmira/types/channel_page_response_guests_item_sentiment.py new file mode 100644 index 0000000..71bb7e7 --- /dev/null +++ b/src/arcmira/types/channel_page_response_guests_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseGuestsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_hosts_detailed_item.py b/src/arcmira/types/channel_page_response_hosts_detailed_item.py new file mode 100644 index 0000000..2f398eb --- /dev/null +++ b/src/arcmira/types/channel_page_response_hosts_detailed_item.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_hosts_detailed_item_sentiment import ChannelPageResponseHostsDetailedItemSentiment + + +class ChannelPageResponseHostsDetailedItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Host name. + """ + + role: str = pydantic.Field() + """ + The recorded role cue, or Host. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Appearances by the host. Null when the plan hides counts. + """ + + sentiment: ChannelPageResponseHostsDetailedItemSentiment = pydantic.Field() + """ + Always positive. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Host image URL. Null until resolved."), + ] = None + """ + Host image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this person. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this person. Null until checked. + """ + + 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/arcmira/types/channel_page_response_hosts_detailed_item_sentiment.py b/src/arcmira/types/channel_page_response_hosts_detailed_item_sentiment.py new file mode 100644 index 0000000..d113254 --- /dev/null +++ b/src/arcmira/types/channel_page_response_hosts_detailed_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseHostsDetailedItemSentiment = typing.Union[typing.Literal["positive"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_organizations_item.py b/src/arcmira/types/channel_page_response_organizations_item.py new file mode 100644 index 0000000..d1ef77f --- /dev/null +++ b/src/arcmira/types/channel_page_response_organizations_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_organizations_item_sentiment import ChannelPageResponseOrganizationsItemSentiment + + +class ChannelPageResponseOrganizationsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: ChannelPageResponseOrganizationsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/channel_page_response_organizations_item_sentiment.py b/src/arcmira/types/channel_page_response_organizations_item_sentiment.py new file mode 100644 index 0000000..1e5879f --- /dev/null +++ b/src/arcmira/types/channel_page_response_organizations_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseOrganizationsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/channel_page_response_products_item.py b/src/arcmira/types/channel_page_response_products_item.py new file mode 100644 index 0000000..3a69347 --- /dev/null +++ b/src/arcmira/types/channel_page_response_products_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .channel_page_response_products_item_sentiment import ChannelPageResponseProductsItemSentiment + + +class ChannelPageResponseProductsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: ChannelPageResponseProductsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/channel_page_response_products_item_sentiment.py b/src/arcmira/types/channel_page_response_products_item_sentiment.py new file mode 100644 index 0000000..eff13a6 --- /dev/null +++ b/src/arcmira/types/channel_page_response_products_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseProductsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/channel_page_response_recommendations_summary.py b/src/arcmira/types/channel_page_response_recommendations_summary.py new file mode 100644 index 0000000..a1d5be7 --- /dev/null +++ b/src/arcmira/types/channel_page_response_recommendations_summary.py @@ -0,0 +1,31 @@ +# 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 ChannelPageResponseRecommendationsSummary(UniversalBaseModel): + """ + Sponsor teaser for the channel. + """ + + sponsor_count: int = pydantic.Field() + """ + Recurring sponsors of the channel. 0 when none. + """ + + top_sponsors: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Names of the top three sponsors. Present only on a Pro+ plan when the channel has sponsors. + """ + + 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/arcmira/types/channel_page_response_stats.py b/src/arcmira/types/channel_page_response_stats.py new file mode 100644 index 0000000..aa36ae9 --- /dev/null +++ b/src/arcmira/types/channel_page_response_stats.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 + + +class ChannelPageResponseStats(UniversalBaseModel): + """ + Header vitals. Open on every plan. + """ + + velocity: int = pydantic.Field() + """ + Videos published in the last 90 days. + """ + + sentiment: float = pydantic.Field() + """ + Reserved. Always 0.5. + """ + + reach: str = pydantic.Field() + """ + Average views per video as display text, e.g. 48.2K. + """ + + total: int = pydantic.Field() + """ + Indexed videos of the channel. + """ + + 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/arcmira/types/channel_page_response_topics_item.py b/src/arcmira/types/channel_page_response_topics_item.py new file mode 100644 index 0000000..3493421 --- /dev/null +++ b/src/arcmira/types/channel_page_response_topics_item.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 .channel_page_response_topics_item_sentiment import ChannelPageResponseTopicsItemSentiment + + +class ChannelPageResponseTopicsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Topic name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Videos on the channel with the topic. Null when the plan hides counts. + """ + + sentiment: ChannelPageResponseTopicsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + 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/arcmira/types/channel_page_response_topics_item_sentiment.py b/src/arcmira/types/channel_page_response_topics_item_sentiment.py new file mode 100644 index 0000000..a2dc862 --- /dev/null +++ b/src/arcmira/types/channel_page_response_topics_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelPageResponseTopicsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/channel_sponsor.py b/src/arcmira/types/channel_sponsor.py new file mode 100644 index 0000000..f98cf8f --- /dev/null +++ b/src/arcmira/types/channel_sponsor.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 .channel_sponsor_entity import ChannelSponsorEntity +from .channel_sponsor_sponsor_status import ChannelSponsorSponsorStatus + + +class ChannelSponsor(UniversalBaseModel): + entity: ChannelSponsorEntity = pydantic.Field() + """ + The sponsoring entity. + """ + + ad_reads: int = pydantic.Field() + """ + Number of ad_read recommendation rows for this sponsor on the channel. + """ + + videos: int = pydantic.Field() + """ + Number of distinct videos containing those ad reads. + """ + + first_seen: typing.Optional[str] = pydantic.Field(default=None) + """ + Publish timestamp of the earliest video with an ad read. Null when unknown. + """ + + last_seen: typing.Optional[str] = pydantic.Field(default=None) + """ + Publish timestamp of the most recent video with an ad read. Null when unknown. + """ + + sponsor_status: typing.Optional[ChannelSponsorSponsorStatus] = pydantic.Field(default=None) + """ + Curated known-advertiser record for this sponsor/channel pair. Null unless the pair exists in the curated dataset. + """ + + 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/arcmira/types/channel_sponsor_entity.py b/src/arcmira/types/channel_sponsor_entity.py new file mode 100644 index 0000000..c50c4dd --- /dev/null +++ b/src/arcmira/types/channel_sponsor_entity.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 ChannelSponsorEntity(UniversalBaseModel): + """ + The sponsoring entity. + """ + + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug, the site's canonical id for every type but channel. Null when never slugged. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + 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/arcmira/types/channel_sponsor_sponsor_status.py b/src/arcmira/types/channel_sponsor_sponsor_status.py new file mode 100644 index 0000000..de1d04e --- /dev/null +++ b/src/arcmira/types/channel_sponsor_sponsor_status.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 + + +class ChannelSponsorSponsorStatus(UniversalBaseModel): + """ + Curated known-advertiser record for this sponsor/channel pair. Null unless the pair exists in the curated dataset. + """ + + status: str = pydantic.Field() + """ + Curated sponsorship status from the known-advertisers dataset. Values: active (currently sponsoring), lapsed (no recent ad reads), ended (relationship known to have ended), uncertain (signal too weak to classify). + """ + + ad_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Curated ad count from the known-advertisers dataset. + """ + + first_ad_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Curated first-ad date. Null when not recorded. + """ + + last_ad_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Curated last-ad date. Null when not recorded. + """ + + 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/arcmira/types/channel_sponsors_response.py b/src/arcmira/types/channel_sponsors_response.py new file mode 100644 index 0000000..3550ea4 --- /dev/null +++ b/src/arcmira/types/channel_sponsors_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 .channel_sponsor import ChannelSponsor +from .channel_sponsors_response_access import ChannelSponsorsResponseAccess +from .channel_sponsors_response_channel import ChannelSponsorsResponseChannel +from .channel_sponsors_response_meta import ChannelSponsorsResponseMeta + + +class ChannelSponsorsResponse(UniversalBaseModel): + channel: ChannelSponsorsResponseChannel + sponsors: typing.List[ChannelSponsor] = pydantic.Field() + """ + Recurring sponsors ordered by ad read count (descending). + """ + + meta: ChannelSponsorsResponseMeta + access: typing.Optional[ChannelSponsorsResponseAccess] = pydantic.Field(default=None) + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + 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/arcmira/types/channel_sponsors_response_access.py b/src/arcmira/types/channel_sponsors_response_access.py new file mode 100644 index 0000000..200737f --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_access.py @@ -0,0 +1,68 @@ +# 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 .channel_sponsors_response_access_gate import ChannelSponsorsResponseAccessGate +from .channel_sponsors_response_access_reason import ChannelSponsorsResponseAccessReason +from .channel_sponsors_response_access_type import ChannelSponsorsResponseAccessType +from .channel_sponsors_response_access_unlock import ChannelSponsorsResponseAccessUnlock + + +class ChannelSponsorsResponseAccess(UniversalBaseModel): + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + type: ChannelSponsorsResponseAccessType = pydantic.Field() + """ + The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling. + """ + + code: str = pydantic.Field() + """ + The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first. + """ + + reason: typing.Optional[ChannelSponsorsResponseAccessReason] = pydantic.Field(default=None) + """ + Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable. + """ + + message: str = pydantic.Field() + """ + One plain line. Names the fix or the unlock. + """ + + param: typing.Optional[str] = pydantic.Field(default=None) + """ + The query or body parameter the gate refused, when one did. + """ + + gate: typing.Optional[ChannelSponsorsResponseAccessGate] = pydantic.Field(default=None) + """ + Which boundary refused. Present on every gate error; switch on it without parsing the message. + """ + + unlock: typing.Optional[ChannelSponsorsResponseAccessUnlock] = pydantic.Field(default=None) + """ + How to lift the gate. Present when the gate has an unlock. + """ + + retry_after_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Present on rate gates. Mirrors the Retry-After header. + """ + + doc_url: str + request_id: str + + 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/arcmira/types/channel_sponsors_response_access_gate.py b/src/arcmira/types/channel_sponsors_response_access_gate.py new file mode 100644 index 0000000..9e0be1a --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_access_gate.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelSponsorsResponseAccessGate = typing.Union[ + typing.Literal["rows", "key", "plan", "freshness", "exposure_law", "rate", "pagination"], typing.Any +] diff --git a/src/arcmira/types/channel_sponsors_response_access_reason.py b/src/arcmira/types/channel_sponsors_response_access_reason.py new file mode 100644 index 0000000..29e1506 --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_access_reason.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelSponsorsResponseAccessReason = typing.Union[typing.Literal["no_credential", "invalid", "revoked"], typing.Any] diff --git a/src/arcmira/types/channel_sponsors_response_access_type.py b/src/arcmira/types/channel_sponsors_response_access_type.py new file mode 100644 index 0000000..7ca7594 --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_access_type.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ChannelSponsorsResponseAccessType = typing.Union[ + typing.Literal[ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error", + ], + typing.Any, +] diff --git a/src/arcmira/types/channel_sponsors_response_access_unlock.py b/src/arcmira/types/channel_sponsors_response_access_unlock.py new file mode 100644 index 0000000..2c3024e --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_access_unlock.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 +from .channel_sponsors_response_access_unlock_action import ChannelSponsorsResponseAccessUnlockAction + + +class ChannelSponsorsResponseAccessUnlock(UniversalBaseModel): + """ + How to lift the gate. Present when the gate has an unlock. + """ + + tier: str = pydantic.Field() + """ + The plan that lifts the gate. + """ + + url: str = pydantic.Field() + """ + Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim. + """ + + offer: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Reserved for the agent-discount offer. Always null today. + """ + + action: typing.Optional[ChannelSponsorsResponseAccessUnlockAction] = pydantic.Field(default=None) + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + 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/arcmira/types/channel_sponsors_response_access_unlock_action.py b/src/arcmira/types/channel_sponsors_response_access_unlock_action.py new file mode 100644 index 0000000..b7aff6e --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_access_unlock_action.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 ChannelSponsorsResponseAccessUnlockAction(UniversalBaseModel): + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + kind: str = pydantic.Field() + """ + What the call does. send_signup_code sends a verification code to an address for an account key. + """ + + method: str = pydantic.Field() + """ + HTTP method to use. + """ + + url: str = pydantic.Field() + """ + Absolute endpoint carrying its ?src= attribution. Call it verbatim. + """ + + 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/arcmira/types/channel_sponsors_response_channel.py b/src/arcmira/types/channel_sponsors_response_channel.py new file mode 100644 index 0000000..daa6331 --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_channel.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 ChannelSponsorsResponseChannel(UniversalBaseModel): + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public entity id ("ent_{n}") of the channel. Null when the channel has media in the index but no entity record yet. + """ + + youtube_channel_id: str = pydantic.Field() + """ + YouTube channel id as supplied in the request path. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Channel name. Null when no entity record exists. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + 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/arcmira/types/channel_sponsors_response_meta.py b/src/arcmira/types/channel_sponsors_response_meta.py new file mode 100644 index 0000000..c788846 --- /dev/null +++ b/src/arcmira/types/channel_sponsors_response_meta.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 ChannelSponsorsResponseMeta(UniversalBaseModel): + min_ad_reads: int = pydantic.Field() + """ + The min_ad_reads threshold applied (default 3). + """ + + count: int = pydantic.Field() + """ + Number of sponsors returned. + """ + + total: int = pydantic.Field() + """ + Sponsors in the rollup at the applied threshold. Greater than count only when the plan gate cut the list to the free slice. + """ + + 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/arcmira/types/channel_videos_response.py b/src/arcmira/types/channel_videos_response.py new file mode 100644 index 0000000..7bb8580 --- /dev/null +++ b/src/arcmira/types/channel_videos_response.py @@ -0,0 +1,56 @@ +# 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 .channel_videos_response_channel import ChannelVideosResponseChannel +from .channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem + + +class ChannelVideosResponse(UniversalBaseModel): + channel: ChannelVideosResponseChannel + episodes: typing.List[ChannelVideosResponseEpisodesItem] = pydantic.Field() + """ + Indexed videos of the channel, newest first. + """ + + returned: int + has_more: bool = pydantic.Field() + """ + True when more indexed videos exist past limit in the window. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Signed continuation for the next page. Null on the last page. + """ + + indexed_through: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest publish date among every indexed video of the channel, whatever window was asked for. Null when nothing is indexed. + """ + + index_age_days: typing.Optional[int] = pydantic.Field(default=None) + """ + Whole days between indexed_through and now. Past 30 the note says the index may be behind the channel. Null when nothing is indexed. + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + Same as indexed_through. + """ + + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. + """ + + 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/arcmira/types/channel_videos_response_channel.py b/src/arcmira/types/channel_videos_response_channel.py new file mode 100644 index 0000000..ed8e8fc --- /dev/null +++ b/src/arcmira/types/channel_videos_response_channel.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 ChannelVideosResponseChannel(UniversalBaseModel): + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public entity id ("ent_{n}") of the channel. Null when no channel entity is known. + """ + + youtube_channel_id: str = pydantic.Field() + """ + The channel id as supplied. + """ + + name: typing.Optional[str] = None + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + 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/arcmira/types/channel_videos_response_episodes_item.py b/src/arcmira/types/channel_videos_response_episodes_item.py new file mode 100644 index 0000000..a4dd70a --- /dev/null +++ b/src/arcmira/types/channel_videos_response_episodes_item.py @@ -0,0 +1,50 @@ +# 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 ChannelVideosResponseEpisodesItem(UniversalBaseModel): + video_id: str = pydantic.Field() + """ + 11-character YouTube video id. Pass it to GET /v1/mentions/counts video_ids or GET /v1/transcripts/{video_id}. + """ + + title: typing.Optional[str] = None + published_at: typing.Optional[str] = pydantic.Field(default=None) + """ + ISO publish date on YouTube. + """ + + duration_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Video length in seconds. Null when YouTube reported none. + """ + + view_count: typing.Optional[int] = None + channel_id: str = pydantic.Field() + """ + The channel id as supplied. + """ + + channel_name: typing.Optional[str] = None + channel_page: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + watch_url: str = pydantic.Field() + """ + The episode on arcmira.com, absolute. + """ + + 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/arcmira/types/correction_accepted_response.py b/src/arcmira/types/correction_accepted_response.py new file mode 100644 index 0000000..7d6eb1c --- /dev/null +++ b/src/arcmira/types/correction_accepted_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 .correction_accepted_response_kind import CorrectionAcceptedResponseKind + + +class CorrectionAcceptedResponse(UniversalBaseModel): + ok: bool = pydantic.Field() + """ + Always true on acceptance. + """ + + kind: CorrectionAcceptedResponseKind = pydantic.Field() + """ + The correction kind, echoed back. + """ + + result: typing.Dict[str, typing.Any] = pydantic.Field() + """ + The stored pending-review row (kind-specific fields). Always carries the row id: use it with the matching withdrawal DELETE. + """ + + 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/arcmira/types/correction_accepted_response_kind.py b/src/arcmira/types/correction_accepted_response_kind.py new file mode 100644 index 0000000..2527b13 --- /dev/null +++ b/src/arcmira/types/correction_accepted_response_kind.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CorrectionAcceptedResponseKind = typing.Union[ + typing.Literal["line_edit", "speaker_reassign", "speaker_identify", "add_person", "entity_tag", "segment_rewrite"], + typing.Any, +] diff --git a/src/arcmira/types/correction_seq_mismatch_response.py b/src/arcmira/types/correction_seq_mismatch_response.py new file mode 100644 index 0000000..91256e4 --- /dev/null +++ b/src/arcmira/types/correction_seq_mismatch_response.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class CorrectionSeqMismatchResponse(UniversalBaseModel): + error: str = pydantic.Field() + """ + Always "Out-of-order correction.". + """ + + expected_seq: typing_extensions.Annotated[ + int, + FieldMetadata(alias="expectedSeq"), + pydantic.Field( + alias="expectedSeq", + description="The seq the server expects next for this video. Rebase local counters onto it and resend.", + ), + ] + """ + The seq the server expects next for this video. Rebase local counters onto it and resend. + """ + + 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/arcmira/types/delivery_issue_change.py b/src/arcmira/types/delivery_issue_change.py new file mode 100644 index 0000000..47a84bf --- /dev/null +++ b/src/arcmira/types/delivery_issue_change.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 +from .delivery_issue_change_channel import DeliveryIssueChangeChannel + + +class DeliveryIssueChange(UniversalBaseModel): + """ + For issue_type delivery_issue: targets the delivery row (the correction id) and names the channel that was wrong or never received. + """ + + channel: DeliveryIssueChangeChannel = pydantic.Field() + """ + The delivery channel the issue concerns. Values: email, webhook, slack. + """ + + 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/arcmira/types/delivery_issue_change_channel.py b/src/arcmira/types/delivery_issue_change_channel.py new file mode 100644 index 0000000..6a6ab8a --- /dev/null +++ b/src/arcmira/types/delivery_issue_change_channel.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +DeliveryIssueChangeChannel = typing.Union[typing.Literal["email", "webhook", "slack"], typing.Any] diff --git a/src/arcmira/types/entity.py b/src/arcmira/types/entity.py new file mode 100644 index 0000000..73a952e --- /dev/null +++ b/src/arcmira/types/entity.py @@ -0,0 +1,107 @@ +# 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 Entity(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". Always the canonical entity id. + """ + + numeric_id: int = pydantic.Field() + """ + Raw integer database id of the canonical entity. Prefer the public "ent_{n}" id in requests. + """ + + canonical_id: str = pydantic.Field() + """ + Public id of the canonical entity. Identical to id. + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + platform: typing.Optional[str] = pydantic.Field(default=None) + """ + Source platform for channel entities, e.g. "youtube". Null unless the entity is platform-bound. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Canonical external URL for the entity. Null when none is known. + """ + + image_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity image URL. Null until an image has been resolved. + """ + + image_checked_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Timestamp of the last image resolution attempt. Null until the image pipeline has visited this entity. + """ + + appearance_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of indexed appearance/mention rows for this entity. 0 when never counted. + """ + + owner_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("ent_{n}") of the owning entity, e.g. the organization behind a product. Null unless an ownership link exists. + """ + + is_canonical: bool = pydantic.Field() + """ + True when the id you supplied is the canonical entity. False when your id was merged into this canonical record. + """ + + merged_from_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id you supplied when it differs from the canonical entity, i.e. your id was merged into this record. Null unless a merge redirect happened. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug, the site's canonical id for every type but channel. Null when never slugged. + """ + + route: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative route of this entity's page on arcmira.com, e.g. "/org/ramp", "/person/jane-doe", "/yt/@TBPNLive". Null for a type the site has no page for. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + appearances_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of their appearances on arcmira.com (their page opens on it). Null for every other type. + """ + + mentions_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of mentions of them on arcmira.com, page/mentions. Null for every other type: their page is the mentions list already. + """ + + 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/arcmira/types/entity_card.py b/src/arcmira/types/entity_card.py new file mode 100644 index 0000000..c2bf183 --- /dev/null +++ b/src/arcmira/types/entity_card.py @@ -0,0 +1,77 @@ +# 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 EntityCard(UniversalBaseModel): + id: int = pydantic.Field() + """ + The REQUESTED raw integer entity id (even when it was merged; the card carries the canonical entity's data under the requested id). + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug. Null when the entity has never been slugged. + """ + + type: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + image_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity image URL. Null until an image has been resolved. + """ + + subtitle: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Reserved for a future category/role line. Always null today. + """ + + index_mentions: int = pydantic.Field() + """ + Total indexed mention rows for the entity. 0 when none. + """ + + index_appearances: typing.Optional[int] = pydantic.Field(default=None) + """ + Indexed physical-appearance rows. Only populated for person entities; null for every other type. + """ + + indexed_videos: typing.Optional[int] = pydantic.Field(default=None) + """ + Completed videos published by this channel and indexed by Arcmira. Null for other entity types. + """ + + average_views: typing.Optional[float] = pydantic.Field(default=None) + """ + Mean of known positive view counts for the channel's indexed videos. Null when unavailable or for other types. + """ + + youtube_channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Canonical YouTube channel identifier, when known. + """ + + youtube_handle: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel handle, when known. + """ + + 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/arcmira/types/entity_cards_response.py b/src/arcmira/types/entity_cards_response.py new file mode 100644 index 0000000..8537a59 --- /dev/null +++ b/src/arcmira/types/entity_cards_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 .entity_card import EntityCard + + +class EntityCardsResponse(UniversalBaseModel): + cards: typing.List[EntityCard] = pydantic.Field() + """ + One card per requested id that resolved to an entity, in requested order. Unknown ids are silently dropped. + """ + + 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/arcmira/types/entity_channel_list_response.py b/src/arcmira/types/entity_channel_list_response.py new file mode 100644 index 0000000..8fe3f63 --- /dev/null +++ b/src/arcmira/types/entity_channel_list_response.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_channel_list_response_export_capabilities import EntityChannelListResponseExportCapabilities +from .entity_channel_list_response_items_item import EntityChannelListResponseItemsItem +from .exposure_meta import ExposureMeta + + +class EntityChannelListResponse(UniversalBaseModel): + items: typing.List[EntityChannelListResponseItemsItem] = pydantic.Field() + """ + Channels whose media carry the entity, highest count first by default. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + export_capabilities: typing_extensions.Annotated[ + EntityChannelListResponseExportCapabilities, + FieldMetadata(alias="exportCapabilities"), + pydantic.Field( + alias="exportCapabilities", + description="Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts.", + ), + ] + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/entity_channel_list_response_export_capabilities.py b/src/arcmira/types/entity_channel_list_response_export_capabilities.py new file mode 100644 index 0000000..2394a87 --- /dev/null +++ b/src/arcmira/types/entity_channel_list_response_export_capabilities.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityChannelListResponseExportCapabilities(UniversalBaseModel): + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + highest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="highestCount"), + pydantic.Field( + alias="highestCount", description="Always true: the highest-count ordering is always available." + ), + ] + """ + Always true: the highest-count ordering is always available. + """ + + lowest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="lowestCount"), + pydantic.Field(alias="lowestCount", description="True when the lowest-count ordering is available."), + ] + """ + True when the lowest-count ordering is available. + """ + + alternate_sort: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="alternateSort"), + pydantic.Field(alias="alternateSort", description="True when sort keys other than count are available."), + ] + """ + True when sort keys other than count are available. + """ + + search: bool = pydantic.Field() + """ + True when the q filter is available. + """ + + available_rows: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="availableRows"), + pydantic.Field( + alias="availableRows", + description="Present only when a stored top-N is smaller than the full result set: the rows it holds.", + ), + ] = None + """ + Present only when a stored top-N is smaller than the full result set: the rows it holds. + """ + + 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/arcmira/types/entity_channel_list_response_items_item.py b/src/arcmira/types/entity_channel_list_response_items_item.py new file mode 100644 index 0000000..d873140 --- /dev/null +++ b/src/arcmira/types/entity_channel_list_response_items_item.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityChannelListResponseItemsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id of the channel. + """ + + name: str = pydantic.Field() + """ + Channel name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Distinct media on this channel counted for the entity: appearances for a person, mentions otherwise. Null when the plan hides counts. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Channel avatar URL. Null until an image has been resolved."), + ] = None + """ + Channel avatar URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/entity_detail_recommendations_summary.py b/src/arcmira/types/entity_detail_recommendations_summary.py new file mode 100644 index 0000000..e975a88 --- /dev/null +++ b/src/arcmira/types/entity_detail_recommendations_summary.py @@ -0,0 +1,51 @@ +# 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 EntityDetailRecommendationsSummary(UniversalBaseModel): + """ + Commercial-intelligence rollup. Only present for organization and product entities when the caller has Recommendations API access (a Pro+ plan) and a brand profile exists. + """ + + total_ad_reads: int = pydantic.Field() + """ + Total ad_read rows across all channels. 0 when none. + """ + + total_endorsements: int = pydantic.Field() + """ + Total endorsement rows across all channels. 0 when none. + """ + + unique_shows: int = pydantic.Field() + """ + Number of distinct shows/channels with commercial mentions of this entity. + """ + + first_seen_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Timestamp of the earliest commercial mention. Null until the brand profile has been computed. + """ + + last_seen_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Timestamp of the most recent commercial mention. Null until the brand profile has been computed. + """ + + channels_as_sponsor: int = pydantic.Field() + """ + Number of channels where this entity appears in the curated known-advertisers dataset. + """ + + 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/arcmira/types/entity_detail_response.py b/src/arcmira/types/entity_detail_response.py new file mode 100644 index 0000000..afbc51b --- /dev/null +++ b/src/arcmira/types/entity_detail_response.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 +from .entity import Entity +from .entity_detail_recommendations_summary import EntityDetailRecommendationsSummary + + +class EntityDetailResponse(UniversalBaseModel): + entity: Entity + recommendations_summary: typing.Optional[EntityDetailRecommendationsSummary] = 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/arcmira/types/entity_lookup_response.py b/src/arcmira/types/entity_lookup_response.py new file mode 100644 index 0000000..9c7bab2 --- /dev/null +++ b/src/arcmira/types/entity_lookup_response.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 +from .entity import Entity + + +class EntityLookupResponse(UniversalBaseModel): + entity: Entity + + 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/arcmira/types/entity_momentum_response.py b/src/arcmira/types/entity_momentum_response.py new file mode 100644 index 0000000..7001c7a --- /dev/null +++ b/src/arcmira/types/entity_momentum_response.py @@ -0,0 +1,60 @@ +# 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 .entity_momentum_response_access import EntityMomentumResponseAccess +from .entity_momentum_response_paid_vs_organic import EntityMomentumResponsePaidVsOrganic +from .entity_momentum_response_top_shows_item import EntityMomentumResponseTopShowsItem +from .entity_momentum_response_verdict import EntityMomentumResponseVerdict +from .entity_momentum_response_volume import EntityMomentumResponseVolume +from .entity_ref import EntityRef + + +class EntityMomentumResponse(UniversalBaseModel): + entity: EntityRef + verdict: EntityMomentumResponseVerdict = pydantic.Field() + """ + Absolute delta of the last 30 days against the prior 30. none when the entity has no mentions at all. + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest indexed media that mentions the entity. Lead with it; it is the date the verdict is true as of. + """ + + coverage: str = pydantic.Field() + """ + What the count measures. Always the shows we index, never the whole internet. + """ + + volume: EntityMomentumResponseVolume + top_shows: typing.List[EntityMomentumResponseTopShowsItem] = pydantic.Field() + """ + Up to five channels by mentions in the last 30 days. + """ + + paid_vs_organic: typing.Optional[EntityMomentumResponsePaidVsOrganic] = pydantic.Field(default=None) + """ + Commercial split for the last 30 days. Present only on a Pro+ plan; otherwise access names the gate. + """ + + access: typing.Optional[EntityMomentumResponseAccess] = pydantic.Field(default=None) + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. + """ + + 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/arcmira/types/entity_momentum_response_access.py b/src/arcmira/types/entity_momentum_response_access.py new file mode 100644 index 0000000..b5c3d61 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_access.py @@ -0,0 +1,68 @@ +# 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 .entity_momentum_response_access_gate import EntityMomentumResponseAccessGate +from .entity_momentum_response_access_reason import EntityMomentumResponseAccessReason +from .entity_momentum_response_access_type import EntityMomentumResponseAccessType +from .entity_momentum_response_access_unlock import EntityMomentumResponseAccessUnlock + + +class EntityMomentumResponseAccess(UniversalBaseModel): + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + type: EntityMomentumResponseAccessType = pydantic.Field() + """ + The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling. + """ + + code: str = pydantic.Field() + """ + The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first. + """ + + reason: typing.Optional[EntityMomentumResponseAccessReason] = pydantic.Field(default=None) + """ + Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable. + """ + + message: str = pydantic.Field() + """ + One plain line. Names the fix or the unlock. + """ + + param: typing.Optional[str] = pydantic.Field(default=None) + """ + The query or body parameter the gate refused, when one did. + """ + + gate: typing.Optional[EntityMomentumResponseAccessGate] = pydantic.Field(default=None) + """ + Which boundary refused. Present on every gate error; switch on it without parsing the message. + """ + + unlock: typing.Optional[EntityMomentumResponseAccessUnlock] = pydantic.Field(default=None) + """ + How to lift the gate. Present when the gate has an unlock. + """ + + retry_after_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Present on rate gates. Mirrors the Retry-After header. + """ + + doc_url: str + request_id: str + + 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/arcmira/types/entity_momentum_response_access_gate.py b/src/arcmira/types/entity_momentum_response_access_gate.py new file mode 100644 index 0000000..f6429b8 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_access_gate.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityMomentumResponseAccessGate = typing.Union[ + typing.Literal["rows", "key", "plan", "freshness", "exposure_law", "rate", "pagination"], typing.Any +] diff --git a/src/arcmira/types/entity_momentum_response_access_reason.py b/src/arcmira/types/entity_momentum_response_access_reason.py new file mode 100644 index 0000000..84158d9 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_access_reason.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityMomentumResponseAccessReason = typing.Union[typing.Literal["no_credential", "invalid", "revoked"], typing.Any] diff --git a/src/arcmira/types/entity_momentum_response_access_type.py b/src/arcmira/types/entity_momentum_response_access_type.py new file mode 100644 index 0000000..13cd0b2 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_access_type.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityMomentumResponseAccessType = typing.Union[ + typing.Literal[ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error", + ], + typing.Any, +] diff --git a/src/arcmira/types/entity_momentum_response_access_unlock.py b/src/arcmira/types/entity_momentum_response_access_unlock.py new file mode 100644 index 0000000..7a2a1e6 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_access_unlock.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 +from .entity_momentum_response_access_unlock_action import EntityMomentumResponseAccessUnlockAction + + +class EntityMomentumResponseAccessUnlock(UniversalBaseModel): + """ + How to lift the gate. Present when the gate has an unlock. + """ + + tier: str = pydantic.Field() + """ + The plan that lifts the gate. + """ + + url: str = pydantic.Field() + """ + Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim. + """ + + offer: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Reserved for the agent-discount offer. Always null today. + """ + + action: typing.Optional[EntityMomentumResponseAccessUnlockAction] = pydantic.Field(default=None) + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + 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/arcmira/types/entity_momentum_response_access_unlock_action.py b/src/arcmira/types/entity_momentum_response_access_unlock_action.py new file mode 100644 index 0000000..bafb0b2 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_access_unlock_action.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 EntityMomentumResponseAccessUnlockAction(UniversalBaseModel): + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + kind: str = pydantic.Field() + """ + What the call does. send_signup_code sends a verification code to an address for an account key. + """ + + method: str = pydantic.Field() + """ + HTTP method to use. + """ + + url: str = pydantic.Field() + """ + Absolute endpoint carrying its ?src= attribution. Call it verbatim. + """ + + 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/arcmira/types/entity_momentum_response_paid_vs_organic.py b/src/arcmira/types/entity_momentum_response_paid_vs_organic.py new file mode 100644 index 0000000..438b2d4 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_paid_vs_organic.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 EntityMomentumResponsePaidVsOrganic(UniversalBaseModel): + """ + Commercial split for the last 30 days. Present only on a Pro+ plan; otherwise access names the gate. + """ + + ad_reads: int + endorsements: int + organic: int + + 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/arcmira/types/entity_momentum_response_top_shows_item.py b/src/arcmira/types/entity_momentum_response_top_shows_item.py new file mode 100644 index 0000000..068876d --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_top_shows_item.py @@ -0,0 +1,26 @@ +# 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 EntityMomentumResponseTopShowsItem(UniversalBaseModel): + channel_id: typing.Optional[str] = None + channel_name: typing.Optional[str] = None + channel_page: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + mentions: int + + 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/arcmira/types/entity_momentum_response_verdict.py b/src/arcmira/types/entity_momentum_response_verdict.py new file mode 100644 index 0000000..8df51d8 --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_verdict.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityMomentumResponseVerdict = typing.Union[typing.Literal["accelerating", "flat", "fading", "none"], typing.Any] diff --git a/src/arcmira/types/entity_momentum_response_volume.py b/src/arcmira/types/entity_momentum_response_volume.py new file mode 100644 index 0000000..831f98b --- /dev/null +++ b/src/arcmira/types/entity_momentum_response_volume.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityMomentumResponseVolume(UniversalBaseModel): + mentions7d: typing_extensions.Annotated[ + int, FieldMetadata(alias="mentions_7d"), pydantic.Field(alias="mentions_7d") + ] + mentions30d: typing_extensions.Annotated[ + int, FieldMetadata(alias="mentions_30d"), pydantic.Field(alias="mentions_30d") + ] + mentions_prior30d: typing_extensions.Annotated[ + int, FieldMetadata(alias="mentions_prior_30d"), pydantic.Field(alias="mentions_prior_30d") + ] + delta30d_absolute: typing_extensions.Annotated[ + int, FieldMetadata(alias="delta_30d_absolute"), pydantic.Field(alias="delta_30d_absolute") + ] + delta30d_pct: typing_extensions.Annotated[ + typing.Optional[float], + FieldMetadata(alias="delta_30d_pct"), + pydantic.Field( + alias="delta_30d_pct", + description="Percent change against the prior 30 days. Null when the prior window was zero.", + ), + ] = None + """ + Percent change against the prior 30 days. Null when the prior window was zero. + """ + + mentions90d: typing_extensions.Annotated[ + int, FieldMetadata(alias="mentions_90d"), pydantic.Field(alias="mentions_90d") + ] + total: int = pydantic.Field() + """ + All-time mentions in the index. + """ + + 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/arcmira/types/entity_organization_list_response.py b/src/arcmira/types/entity_organization_list_response.py new file mode 100644 index 0000000..47c07e3 --- /dev/null +++ b/src/arcmira/types/entity_organization_list_response.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_organization_list_response_export_capabilities import EntityOrganizationListResponseExportCapabilities +from .entity_organization_list_response_items_item import EntityOrganizationListResponseItemsItem +from .exposure_meta import ExposureMeta + + +class EntityOrganizationListResponse(UniversalBaseModel): + items: typing.List[EntityOrganizationListResponseItemsItem] = pydantic.Field() + """ + Organizations that co-occur with the entity in indexed media, highest count first by default. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + export_capabilities: typing_extensions.Annotated[ + EntityOrganizationListResponseExportCapabilities, + FieldMetadata(alias="exportCapabilities"), + pydantic.Field( + alias="exportCapabilities", + description="Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts.", + ), + ] + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/entity_organization_list_response_export_capabilities.py b/src/arcmira/types/entity_organization_list_response_export_capabilities.py new file mode 100644 index 0000000..b271e51 --- /dev/null +++ b/src/arcmira/types/entity_organization_list_response_export_capabilities.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityOrganizationListResponseExportCapabilities(UniversalBaseModel): + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + highest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="highestCount"), + pydantic.Field( + alias="highestCount", description="Always true: the highest-count ordering is always available." + ), + ] + """ + Always true: the highest-count ordering is always available. + """ + + lowest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="lowestCount"), + pydantic.Field(alias="lowestCount", description="True when the lowest-count ordering is available."), + ] + """ + True when the lowest-count ordering is available. + """ + + alternate_sort: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="alternateSort"), + pydantic.Field(alias="alternateSort", description="True when sort keys other than count are available."), + ] + """ + True when sort keys other than count are available. + """ + + search: bool = pydantic.Field() + """ + True when the q filter is available. + """ + + available_rows: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="availableRows"), + pydantic.Field( + alias="availableRows", + description="Present only when a stored top-N is smaller than the full result set: the rows it holds.", + ), + ] = None + """ + Present only when a stored top-N is smaller than the full result set: the rows it holds. + """ + + 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/arcmira/types/entity_organization_list_response_items_item.py b/src/arcmira/types/entity_organization_list_response_items_item.py new file mode 100644 index 0000000..83540e6 --- /dev/null +++ b/src/arcmira/types/entity_organization_list_response_items_item.py @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_organization_list_response_items_item_sentiment import EntityOrganizationListResponseItemsItemSentiment + + +class EntityOrganizationListResponseItemsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id of the organization. + """ + + name: str = pydantic.Field() + """ + Organization name. + """ + + type: str = pydantic.Field() + """ + Stored organization type: organization, company, or brand. Rows served from the stored top-N read organization. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: EntityOrganizationListResponseItemsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Organization logo URL. Null until an image has been resolved."), + ] = None + """ + Organization logo URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/entity_organization_list_response_items_item_sentiment.py b/src/arcmira/types/entity_organization_list_response_items_item_sentiment.py new file mode 100644 index 0000000..a72b63e --- /dev/null +++ b/src/arcmira/types/entity_organization_list_response_items_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityOrganizationListResponseItemsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/entity_page_mention.py b/src/arcmira/types/entity_page_mention.py new file mode 100644 index 0000000..2c017dd --- /dev/null +++ b/src/arcmira/types/entity_page_mention.py @@ -0,0 +1,158 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_page_mention_excerpt import EntityPageMentionExcerpt +from .entity_page_mention_platform import EntityPageMentionPlatform +from .entity_page_mention_sentiment import EntityPageMentionSentiment +from .entity_page_mention_type import EntityPageMentionType + + +class EntityPageMention(UniversalBaseModel): + id: str = pydantic.Field() + """ + Raw appearance row id (the first row for the video), as a string. + """ + + date: str = pydantic.Field() + """ + Publish date as locale display text, or Unknown. + """ + + published_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish timestamp. Null when unknown."), + ] = None + """ + Publish timestamp. Null when unknown. + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + channel: str = pydantic.Field() + """ + Source channel name, or Unknown Channel. + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field(alias="channelId", description="YouTube channel id of the source channel. Null when unknown."), + ] = None + """ + YouTube channel id of the source channel. Null when unknown. + """ + + channel_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelHandle"), + pydantic.Field(alias="channelHandle", description="YouTube handle of the source channel. Null when unknown."), + ] = None + """ + YouTube handle of the source channel. Null when unknown. + """ + + platform: EntityPageMentionPlatform = pydantic.Field() + """ + Always youtube. + """ + + thumbnail: str = pydantic.Field() + """ + Video thumbnail URL. + """ + + thumbnail_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="thumbnailUrl"), + pydantic.Field(alias="thumbnailUrl", description="Video thumbnail URL. Same value as thumbnail."), + ] + """ + Video thumbnail URL. Same value as thumbnail. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + duration: typing.Optional[float] = pydantic.Field(default=None) + """ + Video length in seconds. Null when unknown. + """ + + type: EntityPageMentionType = pydantic.Field() + """ + Always mention. + """ + + context: str = pydantic.Field() + """ + Description of the mention, or "No description available". + """ + + sentiment: typing.Optional[EntityPageMentionSentiment] = pydantic.Field(default=None) + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). Absent when no mention in the video carries a score. + """ + + timestamp: typing.Optional[str] = pydantic.Field(default=None) + """ + Earliest mention timestamp as MM:SS text. Null when the mention covers the full episode. + """ + + raw_timestamp: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="rawTimestamp"), + pydantic.Field(alias="rawTimestamp", description="Earliest mention timestamp as stored. Null when none."), + ] = None + """ + Earliest mention timestamp as stored. Null when none. + """ + + mention_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="mentionCount"), + pydantic.Field(alias="mentionCount", description="Mentions of the entity in this video. At least 1."), + ] + """ + Mentions of the entity in this video. At least 1. + """ + + all_timestamps: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], + FieldMetadata(alias="allTimestamps"), + pydantic.Field( + alias="allTimestamps", + description="Every non-zero mention timestamp in the video, as MM:SS text. Absent when there are none.", + ), + ] = None + """ + Every non-zero mention timestamp in the video, as MM:SS text. Absent when there are none. + """ + + excerpt: typing.Optional[EntityPageMentionExcerpt] = pydantic.Field(default=None) + """ + A speakerless excerpt naming the entity in this media, when one is active. + """ + + 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/arcmira/types/entity_page_mention_excerpt.py b/src/arcmira/types/entity_page_mention_excerpt.py new file mode 100644 index 0000000..664c52d --- /dev/null +++ b/src/arcmira/types/entity_page_mention_excerpt.py @@ -0,0 +1,106 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_page_mention_excerpt_public_source_class import EntityPageMentionExcerptPublicSourceClass + + +class EntityPageMentionExcerpt(UniversalBaseModel): + """ + A speakerless excerpt naming the entity in this media, when one is active. + """ + + id: str = pydantic.Field() + """ + Published excerpt id. + """ + + exact_text: typing_extensions.Annotated[ + str, + FieldMetadata(alias="exactText"), + pydantic.Field(alias="exactText", description="The transcript span that names the entity."), + ] + """ + The transcript span that names the entity. + """ + + context_before: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="contextBefore"), + pydantic.Field(alias="contextBefore", description="Transcript text immediately before the span."), + ] = None + """ + Transcript text immediately before the span. + """ + + context_after: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="contextAfter"), + pydantic.Field(alias="contextAfter", description="Transcript text immediately after the span."), + ] = None + """ + Transcript text immediately after the span. + """ + + term: str = pydantic.Field() + """ + The surface form that matched in the transcript, which may be an approved alias. + """ + + term_char_start: typing_extensions.Annotated[ + int, + FieldMetadata(alias="termCharStart"), + pydantic.Field(alias="termCharStart", description="Character offset where term starts."), + ] + """ + Character offset where term starts. + """ + + term_char_end: typing_extensions.Annotated[ + int, + FieldMetadata(alias="termCharEnd"), + pydantic.Field(alias="termCharEnd", description="Character offset where term ends."), + ] + """ + Character offset where term ends. + """ + + start_seconds: typing_extensions.Annotated[ + float, + FieldMetadata(alias="startSeconds"), + pydantic.Field(alias="startSeconds", description="Span start in the video, in seconds."), + ] + """ + Span start in the video, in seconds. + """ + + end_seconds: typing_extensions.Annotated[ + typing.Optional[float], + FieldMetadata(alias="endSeconds"), + pydantic.Field(alias="endSeconds", description="Span end in the video, in seconds."), + ] = None + """ + Span end in the video, in seconds. + """ + + public_source_class: typing_extensions.Annotated[ + EntityPageMentionExcerptPublicSourceClass, + FieldMetadata(alias="publicSourceClass"), + pydantic.Field(alias="publicSourceClass", description="Transcript source class of the excerpt."), + ] + """ + Transcript source class of the 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/arcmira/types/entity_page_mention_excerpt_public_source_class.py b/src/arcmira/types/entity_page_mention_excerpt_public_source_class.py new file mode 100644 index 0000000..15ab32b --- /dev/null +++ b/src/arcmira/types/entity_page_mention_excerpt_public_source_class.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityPageMentionExcerptPublicSourceClass = typing.Union[ + typing.Literal["arcmira_premium_excerpt", "creator_captions", "third_party_quick"], typing.Any +] diff --git a/src/arcmira/types/entity_page_mention_platform.py b/src/arcmira/types/entity_page_mention_platform.py new file mode 100644 index 0000000..38a722f --- /dev/null +++ b/src/arcmira/types/entity_page_mention_platform.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityPageMentionPlatform = typing.Union[typing.Literal["youtube"], typing.Any] diff --git a/src/arcmira/types/entity_page_mention_sentiment.py b/src/arcmira/types/entity_page_mention_sentiment.py new file mode 100644 index 0000000..57c0782 --- /dev/null +++ b/src/arcmira/types/entity_page_mention_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityPageMentionSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/entity_page_mention_type.py b/src/arcmira/types/entity_page_mention_type.py new file mode 100644 index 0000000..08feb1a --- /dev/null +++ b/src/arcmira/types/entity_page_mention_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityPageMentionType = typing.Union[typing.Literal["mention"], typing.Any] diff --git a/src/arcmira/types/entity_people_list_response.py b/src/arcmira/types/entity_people_list_response.py new file mode 100644 index 0000000..ce5f62f --- /dev/null +++ b/src/arcmira/types/entity_people_list_response.py @@ -0,0 +1,88 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_people_list_response_export_capabilities import EntityPeopleListResponseExportCapabilities +from .entity_people_list_response_items_item import EntityPeopleListResponseItemsItem +from .entity_people_list_response_people_mode import EntityPeopleListResponsePeopleMode +from .exposure_meta import ExposureMeta + + +class EntityPeopleListResponse(UniversalBaseModel): + items: typing.List[EntityPeopleListResponseItemsItem] = pydantic.Field() + """ + People that co-occur with the entity in indexed media, highest count first by default. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + people_mode: typing_extensions.Annotated[ + EntityPeopleListResponsePeopleMode, + FieldMetadata(alias="peopleMode"), + pydantic.Field( + alias="peopleMode", + description="Which co-occurrence the rows count. appearances: guest episodes. mentions: any shared media. Defaults to mentions for organizations and products, appearances otherwise.", + ), + ] + """ + Which co-occurrence the rows count. appearances: guest episodes. mentions: any shared media. Defaults to mentions for organizations and products, appearances otherwise. + """ + + export_capabilities: typing_extensions.Annotated[ + EntityPeopleListResponseExportCapabilities, + FieldMetadata(alias="exportCapabilities"), + pydantic.Field( + alias="exportCapabilities", + description="Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts.", + ), + ] + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/entity_people_list_response_export_capabilities.py b/src/arcmira/types/entity_people_list_response_export_capabilities.py new file mode 100644 index 0000000..5b7c63f --- /dev/null +++ b/src/arcmira/types/entity_people_list_response_export_capabilities.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityPeopleListResponseExportCapabilities(UniversalBaseModel): + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + highest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="highestCount"), + pydantic.Field( + alias="highestCount", description="Always true: the highest-count ordering is always available." + ), + ] + """ + Always true: the highest-count ordering is always available. + """ + + lowest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="lowestCount"), + pydantic.Field(alias="lowestCount", description="True when the lowest-count ordering is available."), + ] + """ + True when the lowest-count ordering is available. + """ + + alternate_sort: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="alternateSort"), + pydantic.Field(alias="alternateSort", description="True when sort keys other than count are available."), + ] + """ + True when sort keys other than count are available. + """ + + search: bool = pydantic.Field() + """ + True when the q filter is available. + """ + + available_rows: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="availableRows"), + pydantic.Field( + alias="availableRows", + description="Present only when a stored top-N is smaller than the full result set: the rows it holds.", + ), + ] = None + """ + Present only when a stored top-N is smaller than the full result set: the rows it holds. + """ + + 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/arcmira/types/entity_people_list_response_items_item.py b/src/arcmira/types/entity_people_list_response_items_item.py new file mode 100644 index 0000000..3d2f90d --- /dev/null +++ b/src/arcmira/types/entity_people_list_response_items_item.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_people_list_response_items_item_sentiment import EntityPeopleListResponseItemsItemSentiment + + +class EntityPeopleListResponseItemsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id of the person. + """ + + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: EntityPeopleListResponseItemsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/entity_people_list_response_items_item_sentiment.py b/src/arcmira/types/entity_people_list_response_items_item_sentiment.py new file mode 100644 index 0000000..06b9e2a --- /dev/null +++ b/src/arcmira/types/entity_people_list_response_items_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityPeopleListResponseItemsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/entity_people_list_response_people_mode.py b/src/arcmira/types/entity_people_list_response_people_mode.py new file mode 100644 index 0000000..de0269d --- /dev/null +++ b/src/arcmira/types/entity_people_list_response_people_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityPeopleListResponsePeopleMode = typing.Union[typing.Literal["appearances", "mentions"], typing.Any] diff --git a/src/arcmira/types/entity_product_list_response.py b/src/arcmira/types/entity_product_list_response.py new file mode 100644 index 0000000..065d640 --- /dev/null +++ b/src/arcmira/types/entity_product_list_response.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_product_list_response_export_capabilities import EntityProductListResponseExportCapabilities +from .entity_product_list_response_items_item import EntityProductListResponseItemsItem +from .exposure_meta import ExposureMeta + + +class EntityProductListResponse(UniversalBaseModel): + items: typing.List[EntityProductListResponseItemsItem] = pydantic.Field() + """ + Products related to the entity, highest count first by default. For an organization these are the products it owns that share media with it; for other types, products that co-occur in indexed media. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + export_capabilities: typing_extensions.Annotated[ + EntityProductListResponseExportCapabilities, + FieldMetadata(alias="exportCapabilities"), + pydantic.Field( + alias="exportCapabilities", + description="Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts.", + ), + ] + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/entity_product_list_response_export_capabilities.py b/src/arcmira/types/entity_product_list_response_export_capabilities.py new file mode 100644 index 0000000..4b616f8 --- /dev/null +++ b/src/arcmira/types/entity_product_list_response_export_capabilities.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityProductListResponseExportCapabilities(UniversalBaseModel): + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + highest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="highestCount"), + pydantic.Field( + alias="highestCount", description="Always true: the highest-count ordering is always available." + ), + ] + """ + Always true: the highest-count ordering is always available. + """ + + lowest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="lowestCount"), + pydantic.Field(alias="lowestCount", description="True when the lowest-count ordering is available."), + ] + """ + True when the lowest-count ordering is available. + """ + + alternate_sort: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="alternateSort"), + pydantic.Field(alias="alternateSort", description="True when sort keys other than count are available."), + ] + """ + True when sort keys other than count are available. + """ + + search: bool = pydantic.Field() + """ + True when the q filter is available. + """ + + available_rows: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="availableRows"), + pydantic.Field( + alias="availableRows", + description="Present only when a stored top-N is smaller than the full result set: the rows it holds.", + ), + ] = None + """ + Present only when a stored top-N is smaller than the full result set: the rows it holds. + """ + + 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/arcmira/types/entity_product_list_response_items_item.py b/src/arcmira/types/entity_product_list_response_items_item.py new file mode 100644 index 0000000..3ad2e28 --- /dev/null +++ b/src/arcmira/types/entity_product_list_response_items_item.py @@ -0,0 +1,77 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_product_list_response_items_item_sentiment import EntityProductListResponseItemsItemSentiment + + +class EntityProductListResponseItemsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id of the product. + """ + + name: str = pydantic.Field() + """ + Product name. + """ + + type: str = pydantic.Field() + """ + Stored entity type. Always product. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + avg_sentiment: typing_extensions.Annotated[ + typing.Optional[float], + FieldMetadata(alias="avgSentiment"), + pydantic.Field( + alias="avgSentiment", + description="Raw average sentiment score between -1 and 1. Null when no score was computed.", + ), + ] = None + """ + Raw average sentiment score between -1 and 1. Null when no score was computed. + """ + + sentiment: EntityProductListResponseItemsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Product logo URL. Null until an image has been resolved."), + ] = None + """ + Product logo URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/entity_product_list_response_items_item_sentiment.py b/src/arcmira/types/entity_product_list_response_items_item_sentiment.py new file mode 100644 index 0000000..26d0b64 --- /dev/null +++ b/src/arcmira/types/entity_product_list_response_items_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityProductListResponseItemsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/entity_ref.py b/src/arcmira/types/entity_ref.py new file mode 100644 index 0000000..dedce5c --- /dev/null +++ b/src/arcmira/types/entity_ref.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 EntityRef(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug, the site's canonical id for every type but channel. Null when never slugged. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + 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/arcmira/types/entity_resolve_response.py b/src/arcmira/types/entity_resolve_response.py new file mode 100644 index 0000000..ecfe44f --- /dev/null +++ b/src/arcmira/types/entity_resolve_response.py @@ -0,0 +1,53 @@ +# 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 .entity_resolve_response_ask import EntityResolveResponseAsk +from .entity_resolve_response_confidence import EntityResolveResponseConfidence +from .resolve_candidate import ResolveCandidate +from .resolve_suggestion import ResolveSuggestion + + +class EntityResolveResponse(UniversalBaseModel): + query: str = pydantic.Field() + """ + The q parameter echoed back. + """ + + context: typing.Optional[str] = pydantic.Field(default=None) + """ + The context parameter echoed back. + """ + + confidence: EntityResolveResponseConfidence = pydantic.Field() + """ + exact: one row is named q (or the handle, id or alias), and no better-known person carries the name. single_fuzzy: the only row returned, not an exact name. ambiguous: several exact rows, or an exact row next to a better-known person sharing the name (Jordan the brand vs Michael Jordan). fuzzy: only loose matches. none: no row. + """ + + best: typing.Optional[ResolveCandidate] = None + suggested: ResolveSuggestion + ask: typing.Optional[EntityResolveResponseAsk] = pydantic.Field(default=None) + """ + Set when best and suggested are both null and several rows fit: show the options to the user, or check every option id against the data and answer per row. + """ + + candidates: typing.List[typing.Optional[ResolveCandidate]] = pydantic.Field() + """ + Rows considered: exact names first, then initials, whole-word, spelling and substring matches, each by appearance count. + """ + + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. + """ + + 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/arcmira/types/entity_resolve_response_ask.py b/src/arcmira/types/entity_resolve_response_ask.py new file mode 100644 index 0000000..6ac9834 --- /dev/null +++ b/src/arcmira/types/entity_resolve_response_ask.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 +from .entity_resolve_response_ask_options_item import EntityResolveResponseAskOptionsItem + + +class EntityResolveResponseAsk(UniversalBaseModel): + """ + Set when best and suggested are both null and several rows fit: show the options to the user, or check every option id against the data and answer per row. + """ + + question: str + options: typing.List[EntityResolveResponseAskOptionsItem] + + 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/arcmira/types/entity_resolve_response_ask_options_item.py b/src/arcmira/types/entity_resolve_response_ask_options_item.py new file mode 100644 index 0000000..6ae4f2a --- /dev/null +++ b/src/arcmira/types/entity_resolve_response_ask_options_item.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 EntityResolveResponseAskOptionsItem(UniversalBaseModel): + id: str + name: str + type: str + label: str = pydantic.Field() + """ + One line to show the user: name, type, description, appearance count. + """ + + 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/arcmira/types/entity_resolve_response_confidence.py b/src/arcmira/types/entity_resolve_response_confidence.py new file mode 100644 index 0000000..127ec1a --- /dev/null +++ b/src/arcmira/types/entity_resolve_response_confidence.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityResolveResponseConfidence = typing.Union[ + typing.Literal["exact", "single_fuzzy", "ambiguous", "fuzzy", "none"], typing.Any +] diff --git a/src/arcmira/types/entity_search_response.py b/src/arcmira/types/entity_search_response.py new file mode 100644 index 0000000..1d68c65 --- /dev/null +++ b/src/arcmira/types/entity_search_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 .entity_search_result import EntitySearchResult + + +class EntitySearchResponse(UniversalBaseModel): + data: typing.List[EntitySearchResult] + query: str = pydantic.Field() + """ + The q parameter echoed back. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows matched than limit. There is no cursor; narrow q or pass 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/arcmira/types/entity_search_result.py b/src/arcmira/types/entity_search_result.py new file mode 100644 index 0000000..6353b9f --- /dev/null +++ b/src/arcmira/types/entity_search_result.py @@ -0,0 +1,73 @@ +# 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 .entity_search_result_recommendations_summary import EntitySearchResultRecommendationsSummary + + +class EntitySearchResult(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". + """ + + numeric_id: int = pydantic.Field() + """ + Raw integer database id. + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug. Null when the entity has never been slugged. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + appearance_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of indexed appearance/mention rows. Results are ordered by this, descending. + """ + + youtube_channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id for channel entities. Null for every other type. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + One catalog sentence that tells rows with the same name apart, for example "Common gender-neutral given name or nickname". Null when the catalog has none. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + suggested: bool = pydantic.Field() + """ + True on an exact match with far more traction than any other row of its type, or on the one row a UC id, @handle, or YouTube URL resolves to. Use it without asking. + """ + + recommendations_summary: typing.Optional[EntitySearchResultRecommendationsSummary] = pydantic.Field(default=None) + """ + Only present when the caller has Recommendations API access (a Pro+ plan) and a brand profile exists for the entity. + """ + + 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/arcmira/types/entity_search_result_recommendations_summary.py b/src/arcmira/types/entity_search_result_recommendations_summary.py new file mode 100644 index 0000000..29856e0 --- /dev/null +++ b/src/arcmira/types/entity_search_result_recommendations_summary.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 EntitySearchResultRecommendationsSummary(UniversalBaseModel): + """ + Only present when the caller has Recommendations API access (a Pro+ plan) and a brand profile exists for the entity. + """ + + total_ad_reads: int = pydantic.Field() + """ + Total ad_read rows for this entity. 0 when none. + """ + + total_endorsements: int = pydantic.Field() + """ + Total endorsement rows for this entity. 0 when none. + """ + + unique_channels: int = pydantic.Field() + """ + Number of distinct channels with commercial mentions of this entity. + """ + + 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/arcmira/types/entity_topic_list_response.py b/src/arcmira/types/entity_topic_list_response.py new file mode 100644 index 0000000..361cd6b --- /dev/null +++ b/src/arcmira/types/entity_topic_list_response.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_topic_list_response_export_capabilities import EntityTopicListResponseExportCapabilities +from .entity_topic_list_response_items_item import EntityTopicListResponseItemsItem +from .exposure_meta import ExposureMeta + + +class EntityTopicListResponse(UniversalBaseModel): + items: typing.List[EntityTopicListResponseItemsItem] = pydantic.Field() + """ + Topics that co-occur with the entity in indexed media, highest count first by default. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + export_capabilities: typing_extensions.Annotated[ + EntityTopicListResponseExportCapabilities, + FieldMetadata(alias="exportCapabilities"), + pydantic.Field( + alias="exportCapabilities", + description="Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts.", + ), + ] + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/entity_topic_list_response_export_capabilities.py b/src/arcmira/types/entity_topic_list_response_export_capabilities.py new file mode 100644 index 0000000..e37b677 --- /dev/null +++ b/src/arcmira/types/entity_topic_list_response_export_capabilities.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class EntityTopicListResponseExportCapabilities(UniversalBaseModel): + """ + Which orderings and filters this list can serve right now. A list served from a stored top-N serves only the highest counts. + """ + + highest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="highestCount"), + pydantic.Field( + alias="highestCount", description="Always true: the highest-count ordering is always available." + ), + ] + """ + Always true: the highest-count ordering is always available. + """ + + lowest_count: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="lowestCount"), + pydantic.Field(alias="lowestCount", description="True when the lowest-count ordering is available."), + ] + """ + True when the lowest-count ordering is available. + """ + + alternate_sort: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="alternateSort"), + pydantic.Field(alias="alternateSort", description="True when sort keys other than count are available."), + ] + """ + True when sort keys other than count are available. + """ + + search: bool = pydantic.Field() + """ + True when the q filter is available. + """ + + available_rows: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="availableRows"), + pydantic.Field( + alias="availableRows", + description="Present only when a stored top-N is smaller than the full result set: the rows it holds.", + ), + ] = None + """ + Present only when a stored top-N is smaller than the full result set: the rows it holds. + """ + + 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/arcmira/types/entity_topic_list_response_items_item.py b/src/arcmira/types/entity_topic_list_response_items_item.py new file mode 100644 index 0000000..f6d7752 --- /dev/null +++ b/src/arcmira/types/entity_topic_list_response_items_item.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 .entity_topic_list_response_items_item_sentiment import EntityTopicListResponseItemsItemSentiment + + +class EntityTopicListResponseItemsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id of the topic. + """ + + name: str = pydantic.Field() + """ + Topic name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Distinct media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: EntityTopicListResponseItemsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + 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/arcmira/types/entity_topic_list_response_items_item_sentiment.py b/src/arcmira/types/entity_topic_list_response_items_item_sentiment.py new file mode 100644 index 0000000..d2d8191 --- /dev/null +++ b/src/arcmira/types/entity_topic_list_response_items_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +EntityTopicListResponseItemsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/error.py b/src/arcmira/types/error.py new file mode 100644 index 0000000..686edfc --- /dev/null +++ b/src/arcmira/types/error.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .error_error import ErrorError + + +class Error(UniversalBaseModel): + existing_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="existingId"), + pydantic.Field( + alias="existingId", + description="On tracker_already_exists, the existing tracker id. Reuse it instead of creating another tracker.", + ), + ] = None + """ + On tracker_already_exists, the existing tracker id. Reuse it instead of creating another tracker. + """ + + error: ErrorError + + 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/arcmira/types/error_error.py b/src/arcmira/types/error_error.py new file mode 100644 index 0000000..f044613 --- /dev/null +++ b/src/arcmira/types/error_error.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 +from .error_error_gate import ErrorErrorGate +from .error_error_reason import ErrorErrorReason +from .error_error_type import ErrorErrorType +from .error_error_unlock import ErrorErrorUnlock + + +class ErrorError(UniversalBaseModel): + type: ErrorErrorType = pydantic.Field() + """ + The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling. + """ + + code: str = pydantic.Field() + """ + The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first. + """ + + reason: typing.Optional[ErrorErrorReason] = pydantic.Field(default=None) + """ + Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable. + """ + + message: str = pydantic.Field() + """ + One plain line. Names the fix or the unlock. + """ + + param: typing.Optional[str] = pydantic.Field(default=None) + """ + The query or body parameter the gate refused, when one did. + """ + + gate: typing.Optional[ErrorErrorGate] = pydantic.Field(default=None) + """ + Which boundary refused. Present on every gate error; switch on it without parsing the message. + """ + + unlock: typing.Optional[ErrorErrorUnlock] = pydantic.Field(default=None) + """ + How to lift the gate. Present when the gate has an unlock. + """ + + retry_after_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Present on rate gates. Mirrors the Retry-After header. + """ + + doc_url: str + request_id: str + + 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/arcmira/types/error_error_gate.py b/src/arcmira/types/error_error_gate.py new file mode 100644 index 0000000..7374df4 --- /dev/null +++ b/src/arcmira/types/error_error_gate.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorErrorGate = typing.Union[ + typing.Literal["rows", "key", "plan", "freshness", "exposure_law", "rate", "pagination"], typing.Any +] diff --git a/src/arcmira/types/error_error_reason.py b/src/arcmira/types/error_error_reason.py new file mode 100644 index 0000000..7640f4e --- /dev/null +++ b/src/arcmira/types/error_error_reason.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorErrorReason = typing.Union[typing.Literal["no_credential", "invalid", "revoked"], typing.Any] diff --git a/src/arcmira/types/error_error_type.py b/src/arcmira/types/error_error_type.py new file mode 100644 index 0000000..48565d4 --- /dev/null +++ b/src/arcmira/types/error_error_type.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorErrorType = typing.Union[ + typing.Literal[ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error", + ], + typing.Any, +] diff --git a/src/arcmira/types/error_error_unlock.py b/src/arcmira/types/error_error_unlock.py new file mode 100644 index 0000000..f560fab --- /dev/null +++ b/src/arcmira/types/error_error_unlock.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 +from .error_error_unlock_action import ErrorErrorUnlockAction + + +class ErrorErrorUnlock(UniversalBaseModel): + """ + How to lift the gate. Present when the gate has an unlock. + """ + + tier: str = pydantic.Field() + """ + The plan that lifts the gate. + """ + + url: str = pydantic.Field() + """ + Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim. + """ + + offer: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Reserved for the agent-discount offer. Always null today. + """ + + action: typing.Optional[ErrorErrorUnlockAction] = pydantic.Field(default=None) + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + 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/arcmira/types/error_error_unlock_action.py b/src/arcmira/types/error_error_unlock_action.py new file mode 100644 index 0000000..e6a8c99 --- /dev/null +++ b/src/arcmira/types/error_error_unlock_action.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 ErrorErrorUnlockAction(UniversalBaseModel): + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + kind: str = pydantic.Field() + """ + What the call does. send_signup_code sends a verification code to an address for an account key. + """ + + method: str = pydantic.Field() + """ + HTTP method to use. + """ + + url: str = pydantic.Field() + """ + Absolute endpoint carrying its ?src= attribution. Call it verbatim. + """ + + 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/arcmira/types/exposure_meta.py b/src/arcmira/types/exposure_meta.py new file mode 100644 index 0000000..2ca89dd --- /dev/null +++ b/src/arcmira/types/exposure_meta.py @@ -0,0 +1,313 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta_access import ExposureMetaAccess +from .exposure_meta_credits import ExposureMetaCredits +from .exposure_meta_free_limit import ExposureMetaFreeLimit +from .exposure_meta_limit_action import ExposureMetaLimitAction +from .exposure_meta_limits import ExposureMetaLimits +from .exposure_meta_recent_preview import ExposureMetaRecentPreview +from .exposure_meta_recent_preview_mentions import ExposureMetaRecentPreviewMentions +from .exposure_meta_totals import ExposureMetaTotals +from .exposure_meta_usage_limit_type import ExposureMetaUsageLimitType + + +class ExposureMeta(UniversalBaseModel): + """ + Usage, plan limits, and the access block for this response: what the caller's plan served and what it withheld. + """ + + tier: str = pydantic.Field() + """ + Plan tier of the caller. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise. + """ + + is_authenticated: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isAuthenticated"), + pydantic.Field(alias="isAuthenticated", description="True when the request carried a valid credential."), + ] + """ + True when the request carried a valid credential. + """ + + rows_used: typing_extensions.Annotated[ + float, + FieldMetadata(alias="rowsUsed"), + pydantic.Field(alias="rowsUsed", description="Rows consumed this period, or lifetime rows on the free tier."), + ] + """ + Rows consumed this period, or lifetime rows on the free tier. + """ + + rows_remaining: typing_extensions.Annotated[ + float, + FieldMetadata(alias="rowsRemaining"), + pydantic.Field( + alias="rowsRemaining", description="Rows left this period, or lifetime rows left on the free tier." + ), + ] + """ + Rows left this period, or lifetime rows left on the free tier. + """ + + monthly_rows: typing_extensions.Annotated[ + float, + FieldMetadata(alias="monthlyRows"), + pydantic.Field( + alias="monthlyRows", description="Rows included per period, or the lifetime allocation on the free tier." + ), + ] + """ + Rows included per period, or the lifetime allocation on the free tier. + """ + + is_over_limit: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isOverLimit"), + pydantic.Field(alias="isOverLimit", description="True when rowsUsed has reached monthlyRows."), + ] + """ + True when rowsUsed has reached monthlyRows. + """ + + on_demand_enabled: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="onDemandEnabled"), + pydantic.Field( + alias="onDemandEnabled", description="True when on-demand usage past the included rows is enabled." + ), + ] + """ + True when on-demand usage past the included rows is enabled. + """ + + spend_limit_cents: typing_extensions.Annotated[ + float, + FieldMetadata(alias="spendLimitCents"), + pydantic.Field(alias="spendLimitCents", description="On-demand spend limit in US cents. 0 means unlimited."), + ] + """ + On-demand spend limit in US cents. 0 means unlimited. + """ + + current_spend_cents: typing_extensions.Annotated[ + float, + FieldMetadata(alias="currentSpendCents"), + pydantic.Field(alias="currentSpendCents", description="On-demand spend so far this period, in US cents."), + ] + """ + On-demand spend so far this period, in US cents. + """ + + can_continue: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="canContinue"), + pydantic.Field(alias="canContinue", description="True when the caller can keep reading data right now."), + ] + """ + True when the caller can keep reading data right now. + """ + + showing_full_data: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="showingFullData"), + pydantic.Field( + alias="showingFullData", + description="True when the plan serves full data. False when the response is a preview: counts are null and lists are cut.", + ), + ] + """ + True when the plan serves full data. False when the response is a preview: counts are null and lists are cut. + """ + + usage_limit_type: typing_extensions.Annotated[ + ExposureMetaUsageLimitType, + FieldMetadata(alias="usageLimitType"), + pydantic.Field( + alias="usageLimitType", + description="lifetime on the free tier, whose rows never reset. monthly on paid tiers.", + ), + ] + """ + lifetime on the free tier, whose rows never reset. monthly on paid tiers. + """ + + lifetime_rows_used: typing_extensions.Annotated[ + typing.Optional[float], + FieldMetadata(alias="lifetimeRowsUsed"), + pydantic.Field( + alias="lifetimeRowsUsed", description="Free tier only: lifetime rows used. Absent on paid tiers." + ), + ] = None + """ + Free tier only: lifetime rows used. Absent on paid tiers. + """ + + lifetime_rows_allocated: typing_extensions.Annotated[ + typing.Optional[float], + FieldMetadata(alias="lifetimeRowsAllocated"), + pydantic.Field( + alias="lifetimeRowsAllocated", description="Free tier only: lifetime rows allocated. Absent on paid tiers." + ), + ] = None + """ + Free tier only: lifetime rows allocated. Absent on paid tiers. + """ + + limit_action: typing_extensions.Annotated[ + typing.Optional[ExposureMetaLimitAction], + FieldMetadata(alias="limitAction"), + pydantic.Field( + alias="limitAction", + description="Present only when the caller is over the limit and cannot continue. Names the fix: upgrade_to_pro, upgrade_or_enable_ondemand, upgrade_or_increase_limit, enable_ondemand, increase_limit, or contact_sales.", + ), + ] = None + """ + Present only when the caller is over the limit and cannot continue. Names the fix: upgrade_to_pro, upgrade_or_enable_ondemand, upgrade_or_increase_limit, enable_ondemand, increase_limit, or contact_sales. + """ + + limit_message: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="limitMessage"), + pydantic.Field( + alias="limitMessage", description="Human sentence for the limit banner. Present only alongside limitAction." + ), + ] = None + """ + Human sentence for the limit banner. Present only alongside limitAction. + """ + + limit_cta_href: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="limitCtaHref"), + pydantic.Field( + alias="limitCtaHref", + description="Site path for the limit banner button. Present only alongside limitAction.", + ), + ] = None + """ + Site path for the limit banner button. Present only alongside limitAction. + """ + + limit_upgrade_tier: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="limitUpgradeTier"), + pydantic.Field( + alias="limitUpgradeTier", + description="The next tier that lifts the limit. Present only alongside limitAction when an upgrade exists. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise.", + ), + ] = None + """ + The next tier that lifts the limit. Present only alongside limitAction when an upgrade exists. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise. + """ + + limit_upgrade_tier_name: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="limitUpgradeTierName"), + pydantic.Field(alias="limitUpgradeTierName", description="Display name of limitUpgradeTier."), + ] = None + """ + Display name of limitUpgradeTier. + """ + + credits: typing.Optional[ExposureMetaCredits] = pydantic.Field(default=None) + """ + The month in credits (1 credit is $0.001; a row is 4 credits). Present only when the credits ledger decides access. + """ + + totals: ExposureMetaTotals = pydantic.Field() + """ + True totals behind the returned rows. Each response sets only the keys for its own sections. + """ + + limits: ExposureMetaLimits = pydantic.Field() + """ + Anonymous row limits and the plan's feature flags. + """ + + cost_per_row_cents: typing_extensions.Annotated[ + float, + FieldMetadata(alias="costPerRowCents"), + pydantic.Field( + alias="costPerRowCents", + description="On-demand price per row in US cents, e.g. 0.4. 0 on tiers without on-demand.", + ), + ] + """ + On-demand price per row in US cents, e.g. 0.4. 0 on tiers without on-demand. + """ + + is_overage: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isOverage"), + pydantic.Field( + alias="isOverage", description="True when this request billed rows past the included allowance." + ), + ] + """ + True when this request billed rows past the included allowance. + """ + + free_limit: typing_extensions.Annotated[ + ExposureMetaFreeLimit, + FieldMetadata(alias="freeLimit"), + pydantic.Field(alias="freeLimit", description="The anonymous row limits again, under their older key."), + ] + """ + The anonymous row limits again, under their older key. + """ + + recent_preview: typing_extensions.Annotated[ + typing.Optional[ExposureMetaRecentPreview], + FieldMetadata(alias="recentPreview"), + pydantic.Field( + alias="recentPreview", + description="Present when the plan withholds the newest media: how much is hidden and a few safe teaser rows.", + ), + ] = None + """ + Present when the plan withholds the newest media: how much is hidden and a few safe teaser rows. + """ + + recent_preview_mentions: typing_extensions.Annotated[ + typing.Optional[ExposureMetaRecentPreviewMentions], + FieldMetadata(alias="recentPreviewMentions"), + pydantic.Field( + alias="recentPreviewMentions", description="Person pages only: the same band for the mentions list." + ), + ] = None + """ + Person pages only: the same band for the mentions list. + """ + + access: ExposureMetaAccess = pydantic.Field() + """ + Structured access block: what the plan served and what it withheld. + """ + + dc: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="_dc"), + pydantic.Field( + alias="_dc", + description="Delivery class code the freshness filter ran at. Present only when the plan delays media and the response went through that filter.", + ), + ] = None + """ + Delivery class code the freshness filter ran at. Present only when the plan delays media and the response went through that filter. + """ + + 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/arcmira/types/exposure_meta_access.py b/src/arcmira/types/exposure_meta_access.py new file mode 100644 index 0000000..5447552 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta_access_chart import ExposureMetaAccessChart +from .exposure_meta_access_cls import ExposureMetaAccessCls +from .exposure_meta_access_freshness import ExposureMetaAccessFreshness +from .exposure_meta_access_ladder import ExposureMetaAccessLadder +from .exposure_meta_access_rows import ExposureMetaAccessRows +from .exposure_meta_access_unlock import ExposureMetaAccessUnlock +from .exposure_meta_access_view import ExposureMetaAccessView +from .exposure_meta_access_withheld_item import ExposureMetaAccessWithheldItem + + +class ExposureMetaAccess(UniversalBaseModel): + """ + Structured access block: what the plan served and what it withheld. + """ + + v: float = pydantic.Field() + """ + Access block version. Always 1. + """ + + view: ExposureMetaAccessView = pydantic.Field() + """ + full when the plan serves everything; preview when something was cut. + """ + + cls: ExposureMetaAccessCls = pydantic.Field() + """ + Freshness class of the response. live serves the newest media; the others delay it. + """ + + ladder: ExposureMetaAccessLadder = pydantic.Field() + """ + Where the caller stands on the gate ladder. + """ + + rows: ExposureMetaAccessRows = pydantic.Field() + """ + Rows served, visible, and blurred per section. + """ + + freshness: ExposureMetaAccessFreshness = pydantic.Field() + """ + The freshness gate applied. + """ + + chart: ExposureMetaAccessChart = pydantic.Field() + """ + The timeline the plan serves. + """ + + pagination: bool = pydantic.Field() + """ + True when the plan may page past the first page. + """ + + show_counts: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="showCounts"), + pydantic.Field( + alias="showCounts", description="True when the plan serves counts. When false, row counts are null." + ), + ] + """ + True when the plan serves counts. When false, row counts are null. + """ + + withheld: typing.List[ExposureMetaAccessWithheldItem] = pydantic.Field() + """ + Every resource the plan withheld from this response. Empty when view is full. + """ + + unlock: typing.Optional[ExposureMetaAccessUnlock] = pydantic.Field(default=None) + """ + How to lift the gate. Null when view is full. + """ + + 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/arcmira/types/exposure_meta_access_chart.py b/src/arcmira/types/exposure_meta_access_chart.py new file mode 100644 index 0000000..98ba7e4 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_chart.py @@ -0,0 +1,26 @@ +# 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 ExposureMetaAccessChart(UniversalBaseModel): + """ + The timeline the plan serves. + """ + + months: int = pydantic.Field() + """ + Months of timeline the plan serves. + """ + + 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/arcmira/types/exposure_meta_access_cls.py b/src/arcmira/types/exposure_meta_access_cls.py new file mode 100644 index 0000000..f3b01b6 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_cls.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessCls = typing.Union[typing.Literal["live", "warm", "public", "cold"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_access_freshness.py b/src/arcmira/types/exposure_meta_access_freshness.py new file mode 100644 index 0000000..0e51901 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_freshness.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ExposureMetaAccessFreshness(UniversalBaseModel): + """ + The freshness gate applied. + """ + + code: int = pydantic.Field() + """ + Delivery class code: 0 live, 1 to 3 delayed classes. + """ + + delay_days: typing_extensions.Annotated[ + int, + FieldMetadata(alias="delayDays"), + pydantic.Field(alias="delayDays", description="Days of newest media withheld. 0 when live."), + ] + """ + Days of newest media withheld. 0 when live. + """ + + 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/arcmira/types/exposure_meta_access_ladder.py b/src/arcmira/types/exposure_meta_access_ladder.py new file mode 100644 index 0000000..f5d3a00 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_ladder.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessLadder = typing.Union[typing.Literal["anonymous", "free", "usage_limit", "unlocked"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_access_rows.py b/src/arcmira/types/exposure_meta_access_rows.py new file mode 100644 index 0000000..d95d534 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_rows.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 .exposure_meta_access_rows_entities import ExposureMetaAccessRowsEntities +from .exposure_meta_access_rows_media import ExposureMetaAccessRowsMedia +from .exposure_meta_access_rows_topics import ExposureMetaAccessRowsTopics + + +class ExposureMetaAccessRows(UniversalBaseModel): + """ + Rows served, visible, and blurred per section. + """ + + media: ExposureMetaAccessRowsMedia = pydantic.Field() + """ + Media rows. + """ + + topics: ExposureMetaAccessRowsTopics = pydantic.Field() + """ + Topic sidebar rows. + """ + + entities: ExposureMetaAccessRowsEntities = pydantic.Field() + """ + People, organization, product, and channel sidebar rows. + """ + + 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/arcmira/types/exposure_meta_access_rows_entities.py b/src/arcmira/types/exposure_meta_access_rows_entities.py new file mode 100644 index 0000000..4a49463 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_rows_entities.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 ExposureMetaAccessRowsEntities(UniversalBaseModel): + """ + People, organization, product, and channel sidebar rows. + """ + + served: int = pydantic.Field() + """ + Rows the backend returns for this section. + """ + + visible: int = pydantic.Field() + """ + Rows the site paints unobscured. + """ + + placeholders: int = pydantic.Field() + """ + Blurred rows the site paints after the visible ones. + """ + + 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/arcmira/types/exposure_meta_access_rows_media.py b/src/arcmira/types/exposure_meta_access_rows_media.py new file mode 100644 index 0000000..e8b9eba --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_rows_media.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 ExposureMetaAccessRowsMedia(UniversalBaseModel): + """ + Media rows. + """ + + served: int = pydantic.Field() + """ + Rows the backend returns for this section. + """ + + visible: int = pydantic.Field() + """ + Rows the site paints unobscured. + """ + + placeholders: int = pydantic.Field() + """ + Blurred rows the site paints after the visible ones. + """ + + 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/arcmira/types/exposure_meta_access_rows_topics.py b/src/arcmira/types/exposure_meta_access_rows_topics.py new file mode 100644 index 0000000..1707f43 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_rows_topics.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 ExposureMetaAccessRowsTopics(UniversalBaseModel): + """ + Topic sidebar rows. + """ + + served: int = pydantic.Field() + """ + Rows the backend returns for this section. + """ + + visible: int = pydantic.Field() + """ + Rows the site paints unobscured. + """ + + placeholders: int = pydantic.Field() + """ + Blurred rows the site paints after the visible ones. + """ + + 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/arcmira/types/exposure_meta_access_unlock.py b/src/arcmira/types/exposure_meta_access_unlock.py new file mode 100644 index 0000000..306d172 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_unlock.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta_access_unlock_limit_action import ExposureMetaAccessUnlockLimitAction +from .exposure_meta_access_unlock_src import ExposureMetaAccessUnlockSrc + + +class ExposureMetaAccessUnlock(UniversalBaseModel): + """ + How to lift the gate. Null when view is full. + """ + + tier: str = pydantic.Field() + """ + The tier that removes the gate. Values: anonymous, free, hobby, pro, pro_plus, ultra, teams, enterprise. + """ + + url: str = pydantic.Field() + """ + Absolute upgrade or sign-up URL carrying its ?src= attribution. + """ + + src: ExposureMetaAccessUnlockSrc = pydantic.Field() + """ + The surface the unlock link attributes to. + """ + + limit_action: typing_extensions.Annotated[ + typing.Optional[ExposureMetaAccessUnlockLimitAction], + FieldMetadata(alias="limitAction"), + pydantic.Field( + alias="limitAction", description="The limit fix when the caller is over its limit. Null otherwise." + ), + ] = None + """ + The limit fix when the caller is over its limit. Null otherwise. + """ + + 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/arcmira/types/exposure_meta_access_unlock_limit_action.py b/src/arcmira/types/exposure_meta_access_unlock_limit_action.py new file mode 100644 index 0000000..c7bf8ee --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_unlock_limit_action.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessUnlockLimitAction = typing.Union[ + typing.Literal[ + "upgrade_to_pro", + "upgrade_or_enable_ondemand", + "upgrade_or_increase_limit", + "enable_ondemand", + "increase_limit", + "contact_sales", + ], + typing.Any, +] diff --git a/src/arcmira/types/exposure_meta_access_unlock_src.py b/src/arcmira/types/exposure_meta_access_unlock_src.py new file mode 100644 index 0000000..f38936e --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_unlock_src.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessUnlockSrc = typing.Union[typing.Literal["access-envelope", "api-boundary", "mcp-tool"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_access_view.py b/src/arcmira/types/exposure_meta_access_view.py new file mode 100644 index 0000000..965865a --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_view.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessView = typing.Union[typing.Literal["full", "preview"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_access_withheld_item.py b/src/arcmira/types/exposure_meta_access_withheld_item.py new file mode 100644 index 0000000..92b95a0 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_withheld_item.py @@ -0,0 +1,68 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta_access_withheld_item_kind import ExposureMetaAccessWithheldItemKind +from .exposure_meta_access_withheld_item_param import ExposureMetaAccessWithheldItemParam +from .exposure_meta_access_withheld_item_section import ExposureMetaAccessWithheldItemSection +from .exposure_meta_access_withheld_item_what import ExposureMetaAccessWithheldItemWhat + + +class ExposureMetaAccessWithheldItem(UniversalBaseModel): + kind: ExposureMetaAccessWithheldItemKind = pydantic.Field() + """ + What was withheld. Values: media_rows (media rows past a position), fresh_media (the newest media), sidebar_rows (sidebar rows past a position), counts (row counts), chart (the timeline), pagination (pages past the first). + """ + + what: ExposureMetaAccessWithheldItemWhat = pydantic.Field() + """ + Repeats kind, for readers written against the older key. + """ + + beyond_row: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="beyondRow"), + pydantic.Field( + alias="beyondRow", description="media_rows and sidebar_rows only: rows past this position are withheld." + ), + ] = None + """ + media_rows and sidebar_rows only: rows past this position are withheld. + """ + + window_days: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="windowDays"), + pydantic.Field(alias="windowDays", description="fresh_media only: days of newest media withheld."), + ] = None + """ + fresh_media only: days of newest media withheld. + """ + + cutoff: typing.Optional[str] = pydantic.Field(default=None) + """ + fresh_media only: the publish date cutoff. Null when none applies. + """ + + section: typing.Optional[ExposureMetaAccessWithheldItemSection] = pydantic.Field(default=None) + """ + sidebar_rows only: which sidebar section. + """ + + param: typing.Optional[ExposureMetaAccessWithheldItemParam] = pydantic.Field(default=None) + """ + pagination only: the paging parameter that was refused. Null when none was sent. + """ + + 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/arcmira/types/exposure_meta_access_withheld_item_kind.py b/src/arcmira/types/exposure_meta_access_withheld_item_kind.py new file mode 100644 index 0000000..d2984c3 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_withheld_item_kind.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessWithheldItemKind = typing.Union[ + typing.Literal["media_rows", "fresh_media", "sidebar_rows", "counts", "chart", "pagination"], typing.Any +] diff --git a/src/arcmira/types/exposure_meta_access_withheld_item_param.py b/src/arcmira/types/exposure_meta_access_withheld_item_param.py new file mode 100644 index 0000000..dc0f561 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_withheld_item_param.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessWithheldItemParam = typing.Union[typing.Literal["offset", "cursor"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_access_withheld_item_section.py b/src/arcmira/types/exposure_meta_access_withheld_item_section.py new file mode 100644 index 0000000..904a3a8 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_withheld_item_section.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessWithheldItemSection = typing.Union[typing.Literal["topics", "entities"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_access_withheld_item_what.py b/src/arcmira/types/exposure_meta_access_withheld_item_what.py new file mode 100644 index 0000000..a08db50 --- /dev/null +++ b/src/arcmira/types/exposure_meta_access_withheld_item_what.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaAccessWithheldItemWhat = typing.Union[ + typing.Literal["media_rows", "fresh_media", "sidebar_rows", "counts", "chart", "pagination"], typing.Any +] diff --git a/src/arcmira/types/exposure_meta_credits.py b/src/arcmira/types/exposure_meta_credits.py new file mode 100644 index 0000000..46a3bad --- /dev/null +++ b/src/arcmira/types/exposure_meta_credits.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 .exposure_meta_credits_on_demand import ExposureMetaCreditsOnDemand +from .exposure_meta_credits_plan import ExposureMetaCreditsPlan + + +class ExposureMetaCredits(UniversalBaseModel): + """ + The month in credits (1 credit is $0.001; a row is 4 credits). Present only when the credits ledger decides access. + """ + + available: typing.Optional[int] = pydantic.Field(default=None) + """ + Credits spendable now: plan, granted, purchased, and on-demand up to its cap. Null when nothing limits it. + """ + + plan: ExposureMetaCreditsPlan + granted: int = pydantic.Field() + """ + Credits left in granted lots that have not expired. + """ + + purchased: int = pydantic.Field() + """ + Credits left in purchased top-ups. + """ + + on_demand: ExposureMetaCreditsOnDemand + + 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/arcmira/types/exposure_meta_credits_on_demand.py b/src/arcmira/types/exposure_meta_credits_on_demand.py new file mode 100644 index 0000000..894e0e8 --- /dev/null +++ b/src/arcmira/types/exposure_meta_credits_on_demand.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 ExposureMetaCreditsOnDemand(UniversalBaseModel): + enabled: bool = pydantic.Field() + """ + True when on-demand credits are on. + """ + + cap_credits: typing.Optional[int] = pydantic.Field(default=None) + """ + The on-demand cap in credits, at $0.002 a credit. Null when uncapped or off. + """ + + used: int = pydantic.Field() + """ + On-demand credits spent this month. + """ + + 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/arcmira/types/exposure_meta_credits_plan.py b/src/arcmira/types/exposure_meta_credits_plan.py new file mode 100644 index 0000000..a04df7e --- /dev/null +++ b/src/arcmira/types/exposure_meta_credits_plan.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 ExposureMetaCreditsPlan(UniversalBaseModel): + credits: typing.Optional[int] = pydantic.Field(default=None) + """ + Plan credits this month. Null when the plan has no limit. + """ + + used: int = pydantic.Field() + """ + Plan credits spent this month. + """ + + resets_at: str = pydantic.Field() + """ + YYYY-MM-DD, the first day of next month (UTC), when plan credits reset. + """ + + 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/arcmira/types/exposure_meta_free_limit.py b/src/arcmira/types/exposure_meta_free_limit.py new file mode 100644 index 0000000..dc9eee2 --- /dev/null +++ b/src/arcmira/types/exposure_meta_free_limit.py @@ -0,0 +1,31 @@ +# 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 ExposureMetaFreeLimit(UniversalBaseModel): + """ + The anonymous row limits again, under their older key. + """ + + appearances: int = pydantic.Field() + """ + Same as limits.freeAppearances. + """ + + entities: int = pydantic.Field() + """ + Same as limits.freeEntitiesPerType. + """ + + 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/arcmira/types/exposure_meta_limit_action.py b/src/arcmira/types/exposure_meta_limit_action.py new file mode 100644 index 0000000..683f1a2 --- /dev/null +++ b/src/arcmira/types/exposure_meta_limit_action.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaLimitAction = typing.Union[ + typing.Literal[ + "upgrade_to_pro", + "upgrade_or_enable_ondemand", + "upgrade_or_increase_limit", + "enable_ondemand", + "increase_limit", + "contact_sales", + ], + typing.Any, +] diff --git a/src/arcmira/types/exposure_meta_limits.py b/src/arcmira/types/exposure_meta_limits.py new file mode 100644 index 0000000..d74a4da --- /dev/null +++ b/src/arcmira/types/exposure_meta_limits.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ExposureMetaLimits(UniversalBaseModel): + """ + Anonymous row limits and the plan's feature flags. + """ + + free_appearances: typing_extensions.Annotated[ + int, + FieldMetadata(alias="freeAppearances"), + pydantic.Field(alias="freeAppearances", description="Media rows an anonymous caller is served."), + ] + """ + Media rows an anonymous caller is served. + """ + + free_entities_per_type: typing_extensions.Annotated[ + int, + FieldMetadata(alias="freeEntitiesPerType"), + pydantic.Field( + alias="freeEntitiesPerType", description="Sidebar rows per entity type an anonymous caller is served." + ), + ] + """ + Sidebar rows per entity type an anonymous caller is served. + """ + + webhooks_enabled: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="webhooksEnabled"), + pydantic.Field(alias="webhooksEnabled", description="True when the plan includes webhook delivery."), + ] + """ + True when the plan includes webhook delivery. + """ + + export_enabled: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="exportEnabled"), + pydantic.Field(alias="exportEnabled", description="True when the plan includes export."), + ] + """ + True when the plan includes export. + """ + + api_enabled: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="apiEnabled"), + pydantic.Field(alias="apiEnabled", description="True when the plan includes API access."), + ] + """ + True when the plan includes API access. + """ + + 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/arcmira/types/exposure_meta_recent_preview.py b/src/arcmira/types/exposure_meta_recent_preview.py new file mode 100644 index 0000000..c6fe416 --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview.py @@ -0,0 +1,89 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta_recent_preview_experiment import ExposureMetaRecentPreviewExperiment +from .exposure_meta_recent_preview_subject import ExposureMetaRecentPreviewSubject +from .exposure_meta_recent_preview_teaser_items_item import ExposureMetaRecentPreviewTeaserItemsItem + + +class ExposureMetaRecentPreview(UniversalBaseModel): + """ + Present when the plan withholds the newest media: how much is hidden and a few safe teaser rows. + """ + + window_days: typing_extensions.Annotated[ + int, + FieldMetadata(alias="windowDays"), + pydantic.Field(alias="windowDays", description="Days of newest media the plan withholds."), + ] + """ + Days of newest media the plan withholds. + """ + + hidden_recent_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="hiddenRecentCount"), + pydantic.Field(alias="hiddenRecentCount", description="Media rows inside the withheld window."), + ] + """ + Media rows inside the withheld window. + """ + + free_account_unlock_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="freeAccountUnlockCount"), + pydantic.Field( + alias="freeAccountUnlockCount", + description="Rows a free account would unlock. Present only for anonymous callers (freshness code 2).", + ), + ] = None + """ + Rows a free account would unlock. Present only for anonymous callers (freshness code 2). + """ + + newest_hidden_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="newestHiddenAt"), + pydantic.Field( + alias="newestHiddenAt", + description="Newest publish date inside the withheld window. Absent when nothing is withheld.", + ), + ] = None + """ + Newest publish date inside the withheld window. Absent when nothing is withheld. + """ + + teaser_items: typing_extensions.Annotated[ + typing.Optional[typing.List[ExposureMetaRecentPreviewTeaserItemsItem]], + FieldMetadata(alias="teaserItems"), + pydantic.Field( + alias="teaserItems", description="Up to three of the newest withheld rows, with safe fields only." + ), + ] = None + """ + Up to three of the newest withheld rows, with safe fields only. + """ + + subject: ExposureMetaRecentPreviewSubject = pydantic.Field() + """ + Which list the withheld rows belong to. + """ + + experiment: ExposureMetaRecentPreviewExperiment = pydantic.Field() + """ + The copy experiment this band feeds. Always recent-intel-gate-copy-v1. + """ + + 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/arcmira/types/exposure_meta_recent_preview_experiment.py b/src/arcmira/types/exposure_meta_recent_preview_experiment.py new file mode 100644 index 0000000..2f3d4b2 --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_experiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaRecentPreviewExperiment = typing.Union[typing.Literal["recent-intel-gate-copy-v1"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_recent_preview_mentions.py b/src/arcmira/types/exposure_meta_recent_preview_mentions.py new file mode 100644 index 0000000..30d6b8a --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_mentions.py @@ -0,0 +1,89 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta_recent_preview_mentions_experiment import ExposureMetaRecentPreviewMentionsExperiment +from .exposure_meta_recent_preview_mentions_subject import ExposureMetaRecentPreviewMentionsSubject +from .exposure_meta_recent_preview_mentions_teaser_items_item import ExposureMetaRecentPreviewMentionsTeaserItemsItem + + +class ExposureMetaRecentPreviewMentions(UniversalBaseModel): + """ + Person pages only: the same band for the mentions list. + """ + + window_days: typing_extensions.Annotated[ + int, + FieldMetadata(alias="windowDays"), + pydantic.Field(alias="windowDays", description="Days of newest media the plan withholds."), + ] + """ + Days of newest media the plan withholds. + """ + + hidden_recent_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="hiddenRecentCount"), + pydantic.Field(alias="hiddenRecentCount", description="Media rows inside the withheld window."), + ] + """ + Media rows inside the withheld window. + """ + + free_account_unlock_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="freeAccountUnlockCount"), + pydantic.Field( + alias="freeAccountUnlockCount", + description="Rows a free account would unlock. Present only for anonymous callers (freshness code 2).", + ), + ] = None + """ + Rows a free account would unlock. Present only for anonymous callers (freshness code 2). + """ + + newest_hidden_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="newestHiddenAt"), + pydantic.Field( + alias="newestHiddenAt", + description="Newest publish date inside the withheld window. Absent when nothing is withheld.", + ), + ] = None + """ + Newest publish date inside the withheld window. Absent when nothing is withheld. + """ + + teaser_items: typing_extensions.Annotated[ + typing.Optional[typing.List[ExposureMetaRecentPreviewMentionsTeaserItemsItem]], + FieldMetadata(alias="teaserItems"), + pydantic.Field( + alias="teaserItems", description="Up to three of the newest withheld rows, with safe fields only." + ), + ] = None + """ + Up to three of the newest withheld rows, with safe fields only. + """ + + subject: ExposureMetaRecentPreviewMentionsSubject = pydantic.Field() + """ + Which list the withheld rows belong to. + """ + + experiment: ExposureMetaRecentPreviewMentionsExperiment = pydantic.Field() + """ + The copy experiment this band feeds. Always recent-intel-gate-copy-v1. + """ + + 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/arcmira/types/exposure_meta_recent_preview_mentions_experiment.py b/src/arcmira/types/exposure_meta_recent_preview_mentions_experiment.py new file mode 100644 index 0000000..24f719a --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_mentions_experiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaRecentPreviewMentionsExperiment = typing.Union[typing.Literal["recent-intel-gate-copy-v1"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_recent_preview_mentions_subject.py b/src/arcmira/types/exposure_meta_recent_preview_mentions_subject.py new file mode 100644 index 0000000..3d70654 --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_mentions_subject.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaRecentPreviewMentionsSubject = typing.Union[typing.Literal["appearances", "mentions", "items"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_recent_preview_mentions_teaser_items_item.py b/src/arcmira/types/exposure_meta_recent_preview_mentions_teaser_items_item.py new file mode 100644 index 0000000..5f27e22 --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_mentions_teaser_items_item.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ExposureMetaRecentPreviewMentionsTeaserItemsItem(UniversalBaseModel): + thumbnail_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="thumbnailUrl"), + pydantic.Field(alias="thumbnailUrl", description="Video thumbnail URL."), + ] + """ + Video thumbnail URL. + """ + + channel_name: typing_extensions.Annotated[ + str, FieldMetadata(alias="channelName"), pydantic.Field(alias="channelName", description="Source channel name.") + ] + """ + Source channel name. + """ + + published_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish date of the withheld video."), + ] + """ + Publish date of the withheld video. + """ + + duration: typing.Optional[str] = pydantic.Field(default=None) + """ + Video length as display text. Null when unknown. + """ + + title: str = pydantic.Field() + """ + Video title, truncated server side. + """ + + 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/arcmira/types/exposure_meta_recent_preview_subject.py b/src/arcmira/types/exposure_meta_recent_preview_subject.py new file mode 100644 index 0000000..04b5ac0 --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_subject.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaRecentPreviewSubject = typing.Union[typing.Literal["appearances", "mentions", "items"], typing.Any] diff --git a/src/arcmira/types/exposure_meta_recent_preview_teaser_items_item.py b/src/arcmira/types/exposure_meta_recent_preview_teaser_items_item.py new file mode 100644 index 0000000..5d4c736 --- /dev/null +++ b/src/arcmira/types/exposure_meta_recent_preview_teaser_items_item.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ExposureMetaRecentPreviewTeaserItemsItem(UniversalBaseModel): + thumbnail_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="thumbnailUrl"), + pydantic.Field(alias="thumbnailUrl", description="Video thumbnail URL."), + ] + """ + Video thumbnail URL. + """ + + channel_name: typing_extensions.Annotated[ + str, FieldMetadata(alias="channelName"), pydantic.Field(alias="channelName", description="Source channel name.") + ] + """ + Source channel name. + """ + + published_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish date of the withheld video."), + ] + """ + Publish date of the withheld video. + """ + + duration: typing.Optional[str] = pydantic.Field(default=None) + """ + Video length as display text. Null when unknown. + """ + + title: str = pydantic.Field() + """ + Video title, truncated server side. + """ + + 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/arcmira/types/exposure_meta_totals.py b/src/arcmira/types/exposure_meta_totals.py new file mode 100644 index 0000000..a20a40d --- /dev/null +++ b/src/arcmira/types/exposure_meta_totals.py @@ -0,0 +1,71 @@ +# 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 ExposureMetaTotals(UniversalBaseModel): + """ + True totals behind the returned rows. Each response sets only the keys for its own sections. + """ + + appearances: typing.Optional[int] = pydantic.Field(default=None) + """ + Total media rows: appearances for a person, mentions for other types, episodes for a channel. + """ + + mentions: typing.Optional[int] = pydantic.Field(default=None) + """ + Person pages only: total media that mention the person. + """ + + topics: typing.Optional[int] = pydantic.Field(default=None) + """ + Total related topics. + """ + + people: typing.Optional[int] = pydantic.Field(default=None) + """ + Total related people. + """ + + brands: typing.Optional[int] = pydantic.Field(default=None) + """ + Total related organizations, under the legacy key brands. + """ + + products: typing.Optional[int] = pydantic.Field(default=None) + """ + Total related products. + """ + + channels: typing.Optional[int] = pydantic.Field(default=None) + """ + Total related channels. + """ + + organizations: typing.Optional[int] = pydantic.Field(default=None) + """ + Total related organizations (organizations list). + """ + + guests: typing.Optional[int] = pydantic.Field(default=None) + """ + Channel only: total unique guests. + """ + + hosts: typing.Optional[int] = pydantic.Field(default=None) + """ + Channel only: total hosts. + """ + + 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/arcmira/types/exposure_meta_usage_limit_type.py b/src/arcmira/types/exposure_meta_usage_limit_type.py new file mode 100644 index 0000000..d5388cd --- /dev/null +++ b/src/arcmira/types/exposure_meta_usage_limit_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExposureMetaUsageLimitType = typing.Union[typing.Literal["lifetime", "monthly"], typing.Any] diff --git a/src/arcmira/types/feedback_correction_result.py b/src/arcmira/types/feedback_correction_result.py new file mode 100644 index 0000000..305ad75 --- /dev/null +++ b/src/arcmira/types/feedback_correction_result.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 +from .feedback_correction_result_recommendation import FeedbackCorrectionResultRecommendation +from .feedback_correction_result_status import FeedbackCorrectionResultStatus + + +class FeedbackCorrectionResult(UniversalBaseModel): + item_id: str = pydantic.Field() + """ + The correction target id you supplied (or a generated placeholder when omitted). + """ + + item_kind: str = pydantic.Field() + """ + Inferred kind of the target. Values include: recommendation, sponsor_entity, entity, entity_merge, mention, appearance, alert, alert_expectation, unknown. + """ + + status: FeedbackCorrectionResultStatus = pydantic.Field() + """ + Outcome. Values: applied (the correction was applied automatically), unchanged (the target already had the requested value), not_found (the target does not exist), invalid (the correction payload was malformed for its kind), logged (recorded for human review; no automatic apply). + """ + + previous_mention_class: typing.Optional[str] = pydantic.Field(default=None) + """ + The mention_class before the correction. Only present for recommendation-class corrections. + """ + + new_mention_class: typing.Optional[str] = pydantic.Field(default=None) + """ + The mention_class requested. Only present for recommendation-class corrections. + """ + + rows_affected: typing.Optional[int] = pydantic.Field(default=None) + """ + Rows updated by an applied correction. + """ + + reason: typing.Optional[str] = pydantic.Field(default=None) + """ + The reason code you supplied, echoed back. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable explanation of the outcome. + """ + + recommendation: typing.Optional[FeedbackCorrectionResultRecommendation] = pydantic.Field(default=None) + """ + The recommendation after the correction. Only present for recommendation-class corrections that resolved a row. + """ + + 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/arcmira/types/feedback_correction_result_recommendation.py b/src/arcmira/types/feedback_correction_result_recommendation.py new file mode 100644 index 0000000..55f668d --- /dev/null +++ b/src/arcmira/types/feedback_correction_result_recommendation.py @@ -0,0 +1,105 @@ +# 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 .entity_ref import EntityRef +from .feedback_correction_result_recommendation_media import FeedbackCorrectionResultRecommendationMedia + + +class FeedbackCorrectionResultRecommendation(UniversalBaseModel): + """ + The recommendation after the correction. Only present for recommendation-class corrections that resolved a row. + """ + + id: str = pydantic.Field() + """ + Public recommendation id in the form "com_{n}". + """ + + recommendation_id: int = pydantic.Field() + """ + Raw integer id of the recommendation row. Same number as in the "com_{n}" public id. + """ + + mention_class: str = pydantic.Field() + """ + Commercial mention classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention). + """ + + entity: EntityRef + media: FeedbackCorrectionResultRecommendationMedia + start_timestamp: str = pydantic.Field() + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds. + """ + + end_timestamp: str = pydantic.Field() + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds. + """ + + start_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Start position in the video in integer SECONDS, parsed from start_timestamp. Prefer this over the deprecated string field. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + end_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + End position in the video in integer SECONDS, parsed from end_timestamp. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + verbatim_quote: typing.Optional[str] = pydantic.Field(default=None) + """ + Verbatim quote from the transcript. Null when no quote was extracted. + """ + + promo_code: typing.Optional[str] = pydantic.Field(default=None) + """ + Promo code read out in the mention. Null unless one was detected. + """ + + offer: typing.Optional[str] = pydantic.Field(default=None) + """ + Offer text, e.g. "20% off your first order". Null unless one was detected. + """ + + sentiment: typing.Optional[float] = pydantic.Field(default=None) + """ + DEPRECATED: use sentiment_score, which carries the same number. Removal will be announced in the changelog. Raw NUMERIC sentiment score between -1 and 1. Null when not computed. + """ + + sentiment_score: typing.Optional[float] = pydantic.Field(default=None) + """ + Raw sentiment score between -1 and 1, same semantics as sentiment_score on mention rows. Null when not computed. + """ + + confidence: float = pydantic.Field() + """ + Classifier confidence between 0 and 1. Rows below the min_confidence filter (default 0.7) are excluded from list responses. + """ + + speaker_role: str = pydantic.Field() + """ + Role of the speaker delivering the mention, e.g. "host" or "guest". + """ + + conflict_status: typing.Optional[str] = pydantic.Field(default=None) + """ + Set when community feedback disputes the classification (e.g. "disputed"). Null when undisputed. Disputed rows are excluded unless include_disputed=true. + """ + + resolution: typing.Optional[str] = pydantic.Field(default=None) + """ + How a disputed classification was resolved. Null until a dispute has been resolved. + """ + + 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/arcmira/types/feedback_correction_result_recommendation_media.py b/src/arcmira/types/feedback_correction_result_recommendation_media.py new file mode 100644 index 0000000..d8b1b9e --- /dev/null +++ b/src/arcmira/types/feedback_correction_result_recommendation_media.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 +from .feedback_correction_result_recommendation_media_source_channel import ( + FeedbackCorrectionResultRecommendationMediaSourceChannel, +) + + +class FeedbackCorrectionResultRecommendationMedia(UniversalBaseModel): + video_id: str = pydantic.Field() + """ + YouTube video id (11 characters). + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + published_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Video publish timestamp. + """ + + channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id of the source channel. + """ + + source_channel: typing.Optional[FeedbackCorrectionResultRecommendationMediaSourceChannel] = pydantic.Field( + default=None + ) + """ + The channel entity that published the video. Null when the video has not been linked to a channel entity. + """ + + 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/arcmira/types/feedback_correction_result_recommendation_media_source_channel.py b/src/arcmira/types/feedback_correction_result_recommendation_media_source_channel.py new file mode 100644 index 0000000..f50316e --- /dev/null +++ b/src/arcmira/types/feedback_correction_result_recommendation_media_source_channel.py @@ -0,0 +1,31 @@ +# 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 FeedbackCorrectionResultRecommendationMediaSourceChannel(UniversalBaseModel): + """ + The channel entity that published the video. Null when the video has not been linked to a channel entity. + """ + + id: str = pydantic.Field() + """ + Public entity id ("ent_{n}") of the source channel. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Source channel 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/arcmira/types/feedback_correction_result_status.py b/src/arcmira/types/feedback_correction_result_status.py new file mode 100644 index 0000000..97ef6e9 --- /dev/null +++ b/src/arcmira/types/feedback_correction_result_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +FeedbackCorrectionResultStatus = typing.Union[ + typing.Literal["applied", "unchanged", "not_found", "invalid", "logged"], typing.Any +] diff --git a/src/arcmira/types/feedback_readback_correction.py b/src/arcmira/types/feedback_readback_correction.py new file mode 100644 index 0000000..090e43b --- /dev/null +++ b/src/arcmira/types/feedback_readback_correction.py @@ -0,0 +1,63 @@ +# 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 .feedback_readback_correction_status import FeedbackReadbackCorrectionStatus + + +class FeedbackReadbackCorrection(UniversalBaseModel): + item_id: str = pydantic.Field() + """ + The correction target id as submitted (or a generated placeholder when omitted). + """ + + item_kind: str = pydantic.Field() + """ + Inferred kind of the target. Values include: recommendation, sponsor_entity, entity, entity_merge, mention, appearance, alert, alert_expectation, unknown. + """ + + issue_type: typing.Optional[str] = pydantic.Field(default=None) + """ + The issue_type as submitted. Null when the correction carried only a reason or mention_class. + """ + + reason: typing.Optional[str] = pydantic.Field(default=None) + """ + The commercial reason code as submitted. Null unless the correction was a commercial-class dispute. + """ + + suggested_change: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + The suggested_change object as submitted. Null when none was supplied. + """ + + notes: typing.Optional[str] = pydantic.Field(default=None) + """ + The per-correction notes as submitted. Null when none were supplied. + """ + + status: FeedbackReadbackCorrectionStatus = pydantic.Field() + """ + Review status in the public vocabulary. Values: pending_review (submitted; a reviewer has not finished with it), needs_information (a reviewer needs more detail from you; add context in a support thread quoting the feedback_id), accepted (the correction was accepted as submitted), accepted_with_changes (accepted, but the reviewer resolved it differently than proposed), rejected (reviewed and declined), withdrawn (withdrawn by the submitter before review), applied (the accepted change is live in the index; accepted does not imply applied), reverted (a previously applied change was rolled back). + """ + + resolution_note: typing.Optional[str] = pydantic.Field(default=None) + """ + Reviewer-written public note about how the correction was resolved. Null until a reviewer leaves one. + """ + + created_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the correction row was created. + """ + + 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/arcmira/types/feedback_readback_correction_status.py b/src/arcmira/types/feedback_readback_correction_status.py new file mode 100644 index 0000000..240874e --- /dev/null +++ b/src/arcmira/types/feedback_readback_correction_status.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +FeedbackReadbackCorrectionStatus = typing.Union[ + typing.Literal[ + "pending_review", + "needs_information", + "accepted", + "accepted_with_changes", + "rejected", + "withdrawn", + "applied", + "reverted", + ], + typing.Any, +] diff --git a/src/arcmira/types/feedback_readback_response.py b/src/arcmira/types/feedback_readback_response.py new file mode 100644 index 0000000..767d94e --- /dev/null +++ b/src/arcmira/types/feedback_readback_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 .feedback_readback_correction import FeedbackReadbackCorrection +from .feedback_readback_response_status import FeedbackReadbackResponseStatus + + +class FeedbackReadbackResponse(UniversalBaseModel): + feedback_id: int = pydantic.Field() + """ + Id of the feedback record. + """ + + type: str = pydantic.Field() + """ + The feedback type as submitted. Values: recommendations, channel_sponsors, mentions, entities_search, entities, channels, monitor_alert, appearances, search. + """ + + status: FeedbackReadbackResponseStatus = pydantic.Field() + """ + Submission-level rollup of the per-correction statuses. Review status in the public vocabulary. Values: pending_review (submitted; a reviewer has not finished with it), needs_information (a reviewer needs more detail from you; add context in a support thread quoting the feedback_id), accepted (the correction was accepted as submitted), accepted_with_changes (accepted, but the reviewer resolved it differently than proposed), rejected (reviewed and declined), withdrawn (withdrawn by the submitter before review), applied (the accepted change is live in the index; accepted does not imply applied), reverted (a previously applied change was rolled back). + """ + + query: typing.Dict[str, typing.Any] = pydantic.Field() + """ + The query object the feedback was attached to, as submitted. + """ + + notes: typing.Optional[str] = pydantic.Field(default=None) + """ + The top-level notes as submitted. Null when none were supplied. + """ + + created_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the submission was created. + """ + + corrections: typing.List[FeedbackReadbackCorrection] = pydantic.Field() + """ + Per-correction rows with their individual review statuses, in submission order. + """ + + 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/arcmira/types/feedback_readback_response_status.py b/src/arcmira/types/feedback_readback_response_status.py new file mode 100644 index 0000000..bfd6e40 --- /dev/null +++ b/src/arcmira/types/feedback_readback_response_status.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +FeedbackReadbackResponseStatus = typing.Union[ + typing.Literal[ + "pending_review", + "needs_information", + "accepted", + "accepted_with_changes", + "rejected", + "withdrawn", + "applied", + "reverted", + ], + typing.Any, +] diff --git a/src/arcmira/types/feedback_response.py b/src/arcmira/types/feedback_response.py new file mode 100644 index 0000000..3d39ea7 --- /dev/null +++ b/src/arcmira/types/feedback_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 .feedback_correction_result import FeedbackCorrectionResult + + +class FeedbackResponse(UniversalBaseModel): + feedback_id: int = pydantic.Field() + """ + Id of the persisted feedback record. Read it back via GET /v1/feedback/{feedback_id}. + """ + + type: str = pydantic.Field() + """ + The feedback type you submitted. Values: recommendations, channel_sponsors, mentions, entities_search, entities, channels, monitor_alert, appearances, search. + """ + + query: typing.Dict[str, typing.Any] = pydantic.Field() + """ + The query object the feedback is attached to, echoed back. + """ + + applied: int = pydantic.Field() + """ + Count of corrections applied automatically. CURRENTLY always 0: public submissions are logged for review, never auto-applied. + """ + + unchanged: int = pydantic.Field() + """ + Count of corrections whose target already had the requested value. Currently always 0 for public submissions. + """ + + failed: int = pydantic.Field() + """ + Count of corrections that could not be processed. Currently always 0 for public submissions. + """ + + logged: int = pydantic.Field() + """ + Count of corrections recorded for human review. + """ + + corrections: typing.List[FeedbackCorrectionResult] = pydantic.Field() + """ + Per-correction outcomes, in submission order. + """ + + 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/arcmira/types/freeform_suggested_change.py b/src/arcmira/types/freeform_suggested_change.py new file mode 100644 index 0000000..5630f7e --- /dev/null +++ b/src/arcmira/types/freeform_suggested_change.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +FreeformSuggestedChange = typing.Dict[str, typing.Any] +""" +Any other object shape. Accepted and logged verbatim for human review; prefer the typed shapes above when one fits your issue_type. +""" diff --git a/src/arcmira/types/health_response.py b/src/arcmira/types/health_response.py new file mode 100644 index 0000000..bbc3a9d --- /dev/null +++ b/src/arcmira/types/health_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 .health_response_status import HealthResponseStatus +from .health_response_version import HealthResponseVersion + + +class HealthResponse(UniversalBaseModel): + status: HealthResponseStatus = pydantic.Field() + """ + Always "ok" when the API is reachable. + """ + + version: HealthResponseVersion = pydantic.Field() + """ + API version. + """ + + time: str = pydantic.Field() + """ + Current server time (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/arcmira/types/health_response_status.py b/src/arcmira/types/health_response_status.py new file mode 100644 index 0000000..8d661a8 --- /dev/null +++ b/src/arcmira/types/health_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +HealthResponseStatus = typing.Union[typing.Literal["ok"], typing.Any] diff --git a/src/arcmira/types/health_response_version.py b/src/arcmira/types/health_response_version.py new file mode 100644 index 0000000..86fb090 --- /dev/null +++ b/src/arcmira/types/health_response_version.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +HealthResponseVersion = typing.Union[typing.Literal["v1"], typing.Any] diff --git a/src/arcmira/types/me_response.py b/src/arcmira/types/me_response.py new file mode 100644 index 0000000..588b57c --- /dev/null +++ b/src/arcmira/types/me_response.py @@ -0,0 +1,73 @@ +# 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 .account_settings import AccountSettings +from .me_response_credential_kind import MeResponseCredentialKind +from .me_response_usage import MeResponseUsage + + +class MeResponse(UniversalBaseModel): + user_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Id of the user the API key belongs to. + """ + + key_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Id of the credential making this request: the account key id, or the OAuth token id. Never a secret. Null on a browser session. + """ + + key_label: typing.Optional[str] = pydantic.Field(default=None) + """ + The key name set in the dashboard, or the name of the connected OAuth client. Null when none is known. + """ + + credential_kind: MeResponseCredentialKind = pydantic.Field() + """ + How the request authenticated: account_key is an arc_sk_ key, oauth is a token from a connected client, session is a signed-in browser. + """ + + email_masked: typing.Optional[str] = pydantic.Field(default=None) + """ + The account email with the local part masked after its first character, e.g. z***@example.com. Null when the account has none. + """ + + period_resets_at: typing.Optional[str] = pydantic.Field(default=None) + """ + ISO 8601 time the monthly row pool resets: 00:00 UTC on the first of next month. Null on the free plan, whose rows are a lifetime pool. + """ + + tier: str = pydantic.Field() + """ + Plan tier, e.g. free, hobby, pro, teams, enterprise. + """ + + scopes: typing.List[str] = pydantic.Field() + """ + Scopes granted to this API key, e.g. read, monitors:write, trackers:write, recommendations:read. + """ + + rate_limit: int = pydantic.Field() + """ + Requests allowed per 60-second window for this key: 600 for enterprise/teams, 240 for other paid tiers, 60 for free, unless a per-key override is set. + """ + + recommendations_api_enabled: bool = pydantic.Field() + """ + True when the plan includes the Recommendations API (commercial intelligence endpoints). + """ + + usage: MeResponseUsage + settings: AccountSettings + + 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/arcmira/types/me_response_credential_kind.py b/src/arcmira/types/me_response_credential_kind.py new file mode 100644 index 0000000..98e4c59 --- /dev/null +++ b/src/arcmira/types/me_response_credential_kind.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MeResponseCredentialKind = typing.Union[typing.Literal["account_key", "oauth", "session"], typing.Any] diff --git a/src/arcmira/types/me_response_usage.py b/src/arcmira/types/me_response_usage.py new file mode 100644 index 0000000..b949e79 --- /dev/null +++ b/src/arcmira/types/me_response_usage.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 .me_response_usage_credits import MeResponseUsageCredits +from .me_response_usage_hits import MeResponseUsageHits + + +class MeResponseUsage(UniversalBaseModel): + rows_used: int = pydantic.Field() + """ + Premium rows consumed this period. + """ + + rows_remaining: int = pydantic.Field() + """ + Premium rows left this period. + """ + + monthly_rows: int = pydantic.Field() + """ + Total premium rows included per period. + """ + + current_spend_cents: int = pydantic.Field() + """ + On-demand overage spend so far this period, in US cents. + """ + + credits: typing.Optional[MeResponseUsageCredits] = pydantic.Field(default=None) + """ + The month in credits (1 credit is $0.001; a row is 4 credits). Present only when the credits ledger decides access. + """ + + hits: typing.Optional[MeResponseUsageHits] = pydantic.Field(default=None) + """ + Monitor alerts this month. Alerts cost no credits; once the allowance is used, monitors keep matching but deliver nothing until the reset. Present only when the credits ledger decides access. + """ + + 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/arcmira/types/me_response_usage_credits.py b/src/arcmira/types/me_response_usage_credits.py new file mode 100644 index 0000000..d8dd699 --- /dev/null +++ b/src/arcmira/types/me_response_usage_credits.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 .me_response_usage_credits_on_demand import MeResponseUsageCreditsOnDemand +from .me_response_usage_credits_plan import MeResponseUsageCreditsPlan + + +class MeResponseUsageCredits(UniversalBaseModel): + """ + The month in credits (1 credit is $0.001; a row is 4 credits). Present only when the credits ledger decides access. + """ + + available: typing.Optional[int] = pydantic.Field(default=None) + """ + Credits spendable now: plan, granted, purchased, and on-demand up to its cap. Null when nothing limits it. + """ + + plan: MeResponseUsageCreditsPlan + granted: int = pydantic.Field() + """ + Credits left in granted lots that have not expired. + """ + + purchased: int = pydantic.Field() + """ + Credits left in purchased top-ups. + """ + + on_demand: MeResponseUsageCreditsOnDemand + + 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/arcmira/types/me_response_usage_credits_on_demand.py b/src/arcmira/types/me_response_usage_credits_on_demand.py new file mode 100644 index 0000000..cea57b6 --- /dev/null +++ b/src/arcmira/types/me_response_usage_credits_on_demand.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 MeResponseUsageCreditsOnDemand(UniversalBaseModel): + enabled: bool = pydantic.Field() + """ + True when on-demand credits are on. + """ + + cap_credits: typing.Optional[int] = pydantic.Field(default=None) + """ + The on-demand cap in credits, at $0.002 a credit. Null when uncapped or off. + """ + + used: int = pydantic.Field() + """ + On-demand credits spent this month. + """ + + 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/arcmira/types/me_response_usage_credits_plan.py b/src/arcmira/types/me_response_usage_credits_plan.py new file mode 100644 index 0000000..2a4b9e8 --- /dev/null +++ b/src/arcmira/types/me_response_usage_credits_plan.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 MeResponseUsageCreditsPlan(UniversalBaseModel): + credits: typing.Optional[int] = pydantic.Field(default=None) + """ + Plan credits this month. Null when the plan has no limit. + """ + + used: int = pydantic.Field() + """ + Plan credits spent this month. + """ + + resets_at: str = pydantic.Field() + """ + YYYY-MM-DD, the first day of next month (UTC), when plan credits reset. + """ + + 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/arcmira/types/me_response_usage_hits.py b/src/arcmira/types/me_response_usage_hits.py new file mode 100644 index 0000000..1093c14 --- /dev/null +++ b/src/arcmira/types/me_response_usage_hits.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 MeResponseUsageHits(UniversalBaseModel): + """ + Monitor alerts this month. Alerts cost no credits; once the allowance is used, monitors keep matching but deliver nothing until the reset. Present only when the credits ledger decides access. + """ + + used: int = pydantic.Field() + """ + Monitor alerts delivered this month. + """ + + allowance: typing.Optional[int] = pydantic.Field(default=None) + """ + Monitor alerts the plan delivers a month. Null when the plan has no limit. + """ + + resets_at: str = pydantic.Field() + """ + YYYY-MM-DD, the first day of next month (UTC), when the count resets. + """ + + 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/arcmira/types/me_settings_response.py b/src/arcmira/types/me_settings_response.py new file mode 100644 index 0000000..946c782 --- /dev/null +++ b/src/arcmira/types/me_settings_response.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 +from .account_settings import AccountSettings + + +class MeSettingsResponse(UniversalBaseModel): + settings: AccountSettings + + 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/arcmira/types/mention.py b/src/arcmira/types/mention.py new file mode 100644 index 0000000..4687cf8 --- /dev/null +++ b/src/arcmira/types/mention.py @@ -0,0 +1,98 @@ +# 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 .entity_ref import EntityRef +from .mention_media import MentionMedia +from .mention_recommendations import MentionRecommendations +from .mention_sentiment import MentionSentiment + + +class Mention(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public mention id in the form "men_{n}". + """ + + appearance_id: int = pydantic.Field() + """ + Raw integer id of the underlying appearance row. Same number as in the "men_{n}" public id. + """ + + entity: EntityRef + media: MentionMedia + start_timestamp: typing.Optional[str] = pydantic.Field(default=None) + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds. Null when the analyzer could not locate the mention in time. + """ + + end_timestamp: typing.Optional[str] = pydantic.Field(default=None) + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds. Null when unknown. + """ + + start_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Start position in the video in integer SECONDS, parsed from start_timestamp. Prefer this over the deprecated string field. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + end_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + End position in the video in integer SECONDS, parsed from end_timestamp. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + is_appearance: bool = pydantic.Field() + """ + True when the person physically appears/speaks in the media (person entities only). Always false for organization, product, topic, and channel entities. Filtering with is_appearance=true on a non-person entity returns a 400 (appearances_person_only). + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + One-sentence description of the mention context. Null when not generated. + """ + + confidence: typing.Optional[float] = pydantic.Field(default=None) + """ + Analyzer confidence between 0 and 1. Null for legacy rows analyzed before confidence scoring. + """ + + sentiment_score: typing.Optional[float] = pydantic.Field(default=None) + """ + Raw sentiment score between -1 and 1. Null when sentiment was not computed for this mention. + """ + + sentiment: MentionSentiment = pydantic.Field() + """ + Derived sentiment label from sentiment_score. Values: positive (score above 0.2), negative (score below -0.2), neutral (score between -0.2 and 0.2 inclusive, or no score computed). + """ + + referenced_url: typing.Optional[str] = pydantic.Field(default=None) + """ + URL referenced in the mention. Null unless one was extracted. + """ + + referenced_platform: typing.Optional[str] = pydantic.Field(default=None) + """ + Platform referenced in the mention, e.g. "twitter". Null unless one was extracted. + """ + + extracted_content: typing.Optional[str] = pydantic.Field(default=None) + """ + Verbatim content extracted for the mention. Null unless extraction ran. + """ + + recommendations: typing.Optional[MentionRecommendations] = pydantic.Field(default=None) + """ + Only present when the request used details=full (requires a Pro+ plan). + """ + + 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/arcmira/types/mention_counts_response.py b/src/arcmira/types/mention_counts_response.py new file mode 100644 index 0000000..64dfc9f --- /dev/null +++ b/src/arcmira/types/mention_counts_response.py @@ -0,0 +1,77 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .mention_counts_response_mode import MentionCountsResponseMode +from .mention_counts_response_rows_item import MentionCountsResponseRowsItem +from .mention_counts_response_shared_item import MentionCountsResponseSharedItem + + +class MentionCountsResponse(UniversalBaseModel): + mode: MentionCountsResponseMode = pydantic.Field() + """ + The mode applied. + """ + + published_after: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="publishedAfter"), pydantic.Field(alias="publishedAfter") + ] = None + published_before: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="publishedBefore"), pydantic.Field(alias="publishedBefore") + ] = None + channel_ids: typing_extensions.Annotated[ + typing.List[str], + FieldMetadata(alias="channelIds"), + pydantic.Field(alias="channelIds", description="The channel ids counted."), + ] + """ + The channel ids counted. + """ + + video_ids: typing_extensions.Annotated[ + typing.List[str], + FieldMetadata(alias="videoIds"), + pydantic.Field(alias="videoIds", description="The video ids the count was scoped to. Empty when it was not."), + ] + """ + The video ids the count was scoped to. Empty when it was not. + """ + + rows: typing.List[MentionCountsResponseRowsItem] = pydantic.Field() + """ + One row per entity and channel, ranked by count. + """ + + returned: int + has_more: bool = pydantic.Field() + """ + True when more entity and channel pairs exist past limit. + """ + + shared: typing.List[MentionCountsResponseSharedItem] = pydantic.Field() + """ + Entities on two or more of the requested channels, ranked by the smallest per-channel count. Empty for one channel. + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest media across rows. Null when there are none. + """ + + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. + """ + + 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/arcmira/types/mention_counts_response_mode.py b/src/arcmira/types/mention_counts_response_mode.py new file mode 100644 index 0000000..39fa5af --- /dev/null +++ b/src/arcmira/types/mention_counts_response_mode.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MentionCountsResponseMode = typing.Union[typing.Literal["mentions", "appearances", "both"], typing.Any] diff --git a/src/arcmira/types/mention_counts_response_rows_item.py b/src/arcmira/types/mention_counts_response_rows_item.py new file mode 100644 index 0000000..8c1311f --- /dev/null +++ b/src/arcmira/types/mention_counts_response_rows_item.py @@ -0,0 +1,66 @@ +# 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 MentionCountsResponseRowsItem(UniversalBaseModel): + entity_id: str = pydantic.Field() + """ + Public entity id ("ent_{n}"). + """ + + name: str + type: str + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + appearances_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of their appearances on arcmira.com (their page opens on it). Null for every other type. + """ + + mentions_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of mentions of them on arcmira.com, page/mentions. Null for every other type: their page is the mentions list already. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug. Null when never slugged. + """ + + channel_id: typing.Optional[str] = None + channel_name: typing.Optional[str] = None + channel_page: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + count: int = pydantic.Field() + """ + Episodes the entity came up in. + """ + + occurrences: int = pydantic.Field() + """ + Times the entity came up across those episodes (mentions or appearances per mode). + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest media in this count. + """ + + 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/arcmira/types/mention_counts_response_shared_item.py b/src/arcmira/types/mention_counts_response_shared_item.py new file mode 100644 index 0000000..16d8241 --- /dev/null +++ b/src/arcmira/types/mention_counts_response_shared_item.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 .mention_counts_response_shared_item_by_channel_item import MentionCountsResponseSharedItemByChannelItem + + +class MentionCountsResponseSharedItem(UniversalBaseModel): + entity_id: str + name: str + type: str + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + appearances_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, their appearances list. Null otherwise. + """ + + mentions_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the mentions of them. Null otherwise. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug. Null when never slugged. + """ + + channel_count: int = pydantic.Field() + """ + How many of the requested channels carry this entity. + """ + + by_channel: typing.List[MentionCountsResponseSharedItemByChannelItem] + + 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/arcmira/types/mention_counts_response_shared_item_by_channel_item.py b/src/arcmira/types/mention_counts_response_shared_item_by_channel_item.py new file mode 100644 index 0000000..36dd4a5 --- /dev/null +++ b/src/arcmira/types/mention_counts_response_shared_item_by_channel_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 MentionCountsResponseSharedItemByChannelItem(UniversalBaseModel): + channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id. + """ + + channel_name: typing.Optional[str] = None + channel_page: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + count: int = pydantic.Field() + """ + Episodes the entity came up in. + """ + + occurrences: int = pydantic.Field() + """ + Times the entity came up across those episodes (mentions or appearances per mode). + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest media in this count. + """ + + 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/arcmira/types/mention_list_response.py b/src/arcmira/types/mention_list_response.py new file mode 100644 index 0000000..a41d785 --- /dev/null +++ b/src/arcmira/types/mention_list_response.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 +from .mention import Mention +from .mention_list_response_entity import MentionListResponseEntity +from .mention_list_response_unlock import MentionListResponseUnlock + + +class MentionListResponse(UniversalBaseModel): + data: typing.List[Mention] + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + entity: MentionListResponseEntity = pydantic.Field() + """ + The resolved entity the mentions belong to. + """ + + note: typing.Optional[str] = pydantic.Field(default=None) + """ + Present on a free preview page: where the list stops and the plan that lifts it. Say so rather than calling this every mention. + """ + + unlock: typing.Optional[MentionListResponseUnlock] = pydantic.Field(default=None) + """ + Present with note on a free preview 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/arcmira/types/mention_list_response_entity.py b/src/arcmira/types/mention_list_response_entity.py new file mode 100644 index 0000000..fa10ce0 --- /dev/null +++ b/src/arcmira/types/mention_list_response_entity.py @@ -0,0 +1,111 @@ +# 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 MentionListResponseEntity(UniversalBaseModel): + """ + The resolved entity the mentions belong to. + """ + + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". Always the canonical entity id. + """ + + numeric_id: int = pydantic.Field() + """ + Raw integer database id of the canonical entity. Prefer the public "ent_{n}" id in requests. + """ + + canonical_id: str = pydantic.Field() + """ + Public id of the canonical entity. Identical to id. + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + platform: typing.Optional[str] = pydantic.Field(default=None) + """ + Source platform for channel entities, e.g. "youtube". Null unless the entity is platform-bound. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Canonical external URL for the entity. Null when none is known. + """ + + image_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity image URL. Null until an image has been resolved. + """ + + image_checked_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Timestamp of the last image resolution attempt. Null until the image pipeline has visited this entity. + """ + + appearance_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of indexed appearance/mention rows for this entity. 0 when never counted. + """ + + owner_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("ent_{n}") of the owning entity, e.g. the organization behind a product. Null unless an ownership link exists. + """ + + is_canonical: bool = pydantic.Field() + """ + True when the id you supplied is the canonical entity. False when your id was merged into this canonical record. + """ + + merged_from_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id you supplied when it differs from the canonical entity, i.e. your id was merged into this record. Null unless a merge redirect happened. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug, the site's canonical id for every type but channel. Null when never slugged. + """ + + route: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative route of this entity's page on arcmira.com, e.g. "/org/ramp", "/person/jane-doe", "/yt/@TBPNLive". Null for a type the site has no page for. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + appearances_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of their appearances on arcmira.com (their page opens on it). Null for every other type. + """ + + mentions_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of mentions of them on arcmira.com, page/mentions. Null for every other type: their page is the mentions list already. + """ + + 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/arcmira/types/mention_list_response_unlock.py b/src/arcmira/types/mention_list_response_unlock.py new file mode 100644 index 0000000..3e9c107 --- /dev/null +++ b/src/arcmira/types/mention_list_response_unlock.py @@ -0,0 +1,31 @@ +# 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 MentionListResponseUnlock(UniversalBaseModel): + """ + Present with note on a free preview page. + """ + + tier: str = pydantic.Field() + """ + The plan that lifts the preview. + """ + + url: str = pydantic.Field() + """ + Where to start that plan. + """ + + 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/arcmira/types/mention_media.py b/src/arcmira/types/mention_media.py new file mode 100644 index 0000000..5a443db --- /dev/null +++ b/src/arcmira/types/mention_media.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 .mention_media_source_channel import MentionMediaSourceChannel + + +class MentionMedia(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer media row id. + """ + + video_id: str = pydantic.Field() + """ + YouTube video id (11 characters). + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. Null when the video was indexed without metadata. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Video URL. Null when unknown. + """ + + published_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Video publish timestamp. Null when unknown. + """ + + channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id of the source channel. Null when unknown. + """ + + view_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Video view count at index time. Null when never fetched. + """ + + source_channel: typing.Optional[MentionMediaSourceChannel] = pydantic.Field(default=None) + """ + The channel entity that published the video. Null when the video has not been linked to a channel entity. + """ + + 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/arcmira/types/mention_media_source_channel.py b/src/arcmira/types/mention_media_source_channel.py new file mode 100644 index 0000000..09f02b8 --- /dev/null +++ b/src/arcmira/types/mention_media_source_channel.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 MentionMediaSourceChannel(UniversalBaseModel): + """ + The channel entity that published the video. Null when the video has not been linked to a channel entity. + """ + + id: str = pydantic.Field() + """ + Public entity id ("ent_{n}") of the source channel. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Source channel name. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Source channel URL. Null when unknown. + """ + + 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/arcmira/types/mention_recommendations.py b/src/arcmira/types/mention_recommendations.py new file mode 100644 index 0000000..4801c07 --- /dev/null +++ b/src/arcmira/types/mention_recommendations.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 +from .recommendation_enrichment_item import RecommendationEnrichmentItem + + +class MentionRecommendations(UniversalBaseModel): + """ + Only present when the request used details=full (requires a Pro+ plan). + """ + + items: typing.List[RecommendationEnrichmentItem] = pydantic.Field() + """ + Commercial mentions (ad reads, endorsements) for the same entity in the same video. + """ + + 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/arcmira/types/mention_sentiment.py b/src/arcmira/types/mention_sentiment.py new file mode 100644 index 0000000..46affd2 --- /dev/null +++ b/src/arcmira/types/mention_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MentionSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/merge_suggestion_change.py b/src/arcmira/types/merge_suggestion_change.py new file mode 100644 index 0000000..e99f192 --- /dev/null +++ b/src/arcmira/types/merge_suggestion_change.py @@ -0,0 +1,68 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class MergeSuggestionChange(UniversalBaseModel): + """ + For issue_type merge_suggestion (and duplicate_entity): the canonical merge you are proposing. Provide ids when you have them, names otherwise. + """ + + source_entity_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="sourceEntityId"), + pydantic.Field( + alias="sourceEntityId", description='Public id ("ent_{n}") of the duplicate/variant entity to merge away.' + ), + ] = None + """ + Public id ("ent_{n}") of the duplicate/variant entity to merge away. + """ + + target_entity_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="targetEntityId"), + pydantic.Field( + alias="targetEntityId", description='Public id ("ent_{n}") of the canonical entity to merge into.' + ), + ] = None + """ + Public id ("ent_{n}") of the canonical entity to merge into. + """ + + source_name: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="sourceName"), + pydantic.Field(alias="sourceName", description="Name of the duplicate entity when you do not have its id."), + ] = None + """ + Name of the duplicate entity when you do not have its id. + """ + + merge_into: typing.Optional[str] = pydantic.Field(default=None) + """ + Name or public id of the canonical entity when you do not have targetEntityId. + """ + + scope_type: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="scopeType"), + pydantic.Field(alias="scopeType", description='Scope of the merge rule, e.g. "global".'), + ] = None + """ + Scope of the merge rule, e.g. "global". + """ + + 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/arcmira/types/message_response.py b/src/arcmira/types/message_response.py new file mode 100644 index 0000000..7b56e8f --- /dev/null +++ b/src/arcmira/types/message_response.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 MessageResponse(UniversalBaseModel): + message: str = pydantic.Field() + """ + Human-readable confirmation. + """ + + 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/arcmira/types/missed_alert_change.py b/src/arcmira/types/missed_alert_change.py new file mode 100644 index 0000000..c270e6d --- /dev/null +++ b/src/arcmira/types/missed_alert_change.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 MissedAlertChange(UniversalBaseModel): + """ + For issue_type missed_alert: an expectation with no alert row to target. Omit the correction id and describe where the alert should have fired. + """ + + source_url: str = pydantic.Field() + """ + URL of the media that should have produced an alert. + """ + + approximate_timestamp_seconds: typing.Optional[float] = pydantic.Field(default=None) + """ + Approximate position of the missed occurrence, in seconds from the start of the media. + """ + + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("ent_{n}") of the tracked entity the missed alert concerns. + """ + + 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/arcmira/types/missing_result_change.py b/src/arcmira/types/missing_result_change.py new file mode 100644 index 0000000..bf5c6a8 --- /dev/null +++ b/src/arcmira/types/missing_result_change.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 MissingResultChange(UniversalBaseModel): + """ + For issue_type missing_result: what should have been returned. Put video/channel/timestamp context in notes. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Name of the missing entity or result. + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Type of the missing entity: person, organization, product, topic, or channel. + """ + + source_url: typing.Optional[str] = pydantic.Field(default=None) + """ + URL evidencing the missing result (video, channel, or article). + """ + + 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/arcmira/types/monitor.py b/src/arcmira/types/monitor.py new file mode 100644 index 0000000..3ecfe60 --- /dev/null +++ b/src/arcmira/types/monitor.py @@ -0,0 +1,240 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .monitor_email_recipients_item import MonitorEmailRecipientsItem + + +class Monitor(UniversalBaseModel): + id: str = pydantic.Field() + """ + Monitor id. + """ + + name: str = pydantic.Field() + """ + Monitor name. + """ + + is_collapsed: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isCollapsed"), + pydantic.Field(alias="isCollapsed", description="True when the monitor is collapsed in the dashboard UI."), + ] + """ + True when the monitor is collapsed in the dashboard UI. + """ + + is_paused: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isPaused"), + pydantic.Field( + alias="isPaused", + description="True when delivery is paused for all trackers in this monitor. New alerts are not queued, and queued email delivery checks the pause state again before sending. Use PATCH /v1/trackers/{id} with paused: true to pause one tracker.", + ), + ] + """ + True when delivery is paused for all trackers in this monitor. New alerts are not queued, and queued email delivery checks the pause state again before sending. Use PATCH /v1/trackers/{id} with paused: true to pause one tracker. + """ + + sort_order: typing_extensions.Annotated[ + int, FieldMetadata(alias="sortOrder"), pydantic.Field(alias="sortOrder", description="Dashboard sort position.") + ] + """ + Dashboard sort position. + """ + + notify_emails: typing_extensions.Annotated[ + typing.List[str], + FieldMetadata(alias="notifyEmails"), + pydantic.Field( + alias="notifyEmails", + description="Configured email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor.", + ), + ] + """ + Configured email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor. + """ + + email_recipients: typing_extensions.Annotated[ + typing.Optional[typing.List[MonitorEmailRecipientsItem]], + FieldMetadata(alias="emailRecipients"), + pydantic.Field( + alias="emailRecipients", + description="Recipient consent and invitation state. An account is not required to accept.", + ), + ] = None + """ + Recipient consent and invitation state. An account is not required to accept. + """ + + notify_frequency: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="notifyFrequency"), + pydantic.Field( + alias="notifyFrequency", + description="Delivery cadence. Values: realtime (deliver immediately), hourly (hourly digest), daily (daily digest). Free tier is limited to daily.", + ), + ] = None + """ + Delivery cadence. Values: realtime (deliver immediately), hourly (hourly digest), daily (daily digest). Free tier is limited to daily. + """ + + digest_day: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="digestDay"), + pydantic.Field(alias="digestDay", description="Day of week for digest delivery."), + ] = None + """ + Day of week for digest delivery. + """ + + digest_time: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="digestTime"), + pydantic.Field(alias="digestTime", description="Time of day (HH:MM) for digest delivery."), + ] = None + """ + Time of day (HH:MM) for digest delivery. + """ + + notify_webhook: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="notifyWebhook"), + pydantic.Field(alias="notifyWebhook", description="True when webhook delivery is enabled."), + ] + """ + True when webhook delivery is enabled. + """ + + webhook_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="webhookUrl"), + pydantic.Field(alias="webhookUrl", description="Webhook destination URL. Null when no webhook is configured."), + ] = None + """ + Webhook destination URL. Null when no webhook is configured. + """ + + webhook_secret_set: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="webhookSecretSet"), + pydantic.Field( + alias="webhookSecretSet", + description="True when a webhook signing secret exists for this monitor. The secret itself is never returned on reads; enablement and rotation responses support recovery with the original Idempotency-Key during the valid recovery window.", + ), + ] + """ + True when a webhook signing secret exists for this monitor. The secret itself is never returned on reads; enablement and rotation responses support recovery with the original Idempotency-Key during the valid recovery window. + """ + + webhook_secret_hint: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="webhookSecretHint"), + pydantic.Field( + alias="webhookSecretHint", + description="Last 4 characters of the current signing secret, for identifying which secret you hold. Null until a secret exists.", + ), + ] = None + """ + Last 4 characters of the current signing secret, for identifying which secret you hold. Null until a secret exists. + """ + + webhook_failures: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="webhookFailures"), + pydantic.Field( + alias="webhookFailures", + description="Consecutive webhook delivery failures recorded for this monitor. Reset by a secret rotation or PATCHing notifyWebhook: true; 10 consecutive failures auto-disable a webhook. Note: the delivery pipeline currently accrues failures on the tracker that fired, so this monitor-level counter can lag.", + ), + ] = None + """ + Consecutive webhook delivery failures recorded for this monitor. Reset by a secret rotation or PATCHing notifyWebhook: true; 10 consecutive failures auto-disable a webhook. Note: the delivery pipeline currently accrues failures on the tracker that fired, so this monitor-level counter can lag. + """ + + webhook_disabled_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="webhookDisabledAt"), + pydantic.Field( + alias="webhookDisabledAt", + description="When the webhook was auto-disabled after repeated failures. Null while delivery is enabled. Re-enable by PATCHing notifyWebhook: true; rotation alone never re-enables.", + ), + ] = None + """ + When the webhook was auto-disabled after repeated failures. Null while delivery is enabled. Re-enable by PATCHing notifyWebhook: true; rotation alone never re-enables. + """ + + webhook_disabled_reason: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="webhookDisabledReason"), + pydantic.Field( + alias="webhookDisabledReason", + description="Why the webhook was auto-disabled. Null while delivery is enabled.", + ), + ] = None + """ + Why the webhook was auto-disabled. Null while delivery is enabled. + """ + + notify_slack: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="notifySlack"), + pydantic.Field(alias="notifySlack", description="True when Slack delivery is enabled."), + ] + """ + True when Slack delivery is enabled. + """ + + slack_integration_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="slackIntegrationId"), + pydantic.Field( + alias="slackIntegrationId", + description="Slack integration used for delivery. Null when Slack is not configured.", + ), + ] = None + """ + Slack integration used for delivery. Null when Slack is not configured. + """ + + slack_channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="slackChannelId"), + pydantic.Field( + alias="slackChannelId", description="Slack channel to deliver to. Null when Slack is not configured." + ), + ] = None + """ + Slack channel to deliver to. Null when Slack is not configured. + """ + + created_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="createdAt"), + pydantic.Field(alias="createdAt", description="When the monitor was created."), + ] + """ + When the monitor was created. + """ + + updated_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="updatedAt"), + pydantic.Field(alias="updatedAt", description="When the monitor was last updated."), + ] + """ + When the monitor was last updated. + """ + + 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/arcmira/types/monitor_add_trackers_response.py b/src/arcmira/types/monitor_add_trackers_response.py new file mode 100644 index 0000000..e4aab09 --- /dev/null +++ b/src/arcmira/types/monitor_add_trackers_response.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class MonitorAddTrackersResponse(UniversalBaseModel): + attached_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="attachedCount"), + pydantic.Field(alias="attachedCount", description="Number of unique requested trackers attached."), + ] + """ + Number of unique requested trackers attached. + """ + + message: str = pydantic.Field() + """ + Human-readable confirmation, e.g. "Added 3 tracker(s) to monitor". + """ + + monitor_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="monitorId"), + pydantic.Field(alias="monitorId", description="The monitor id from the request path."), + ] + """ + The monitor id from the request path. + """ + + 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/arcmira/types/monitor_delete_response.py b/src/arcmira/types/monitor_delete_response.py new file mode 100644 index 0000000..6aba82c --- /dev/null +++ b/src/arcmira/types/monitor_delete_response.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class MonitorDeleteResponse(UniversalBaseModel): + message: str = pydantic.Field() + """ + Human-readable confirmation. + """ + + trackers_deleted: typing_extensions.Annotated[ + int, + FieldMetadata(alias="trackersDeleted"), + pydantic.Field( + alias="trackersDeleted", description="Number of trackers that were deleted along with the monitor." + ), + ] + """ + Number of trackers that were deleted along with the monitor. + """ + + 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/arcmira/types/monitor_email_recipients_item.py b/src/arcmira/types/monitor_email_recipients_item.py new file mode 100644 index 0000000..0fb503f --- /dev/null +++ b/src/arcmira/types/monitor_email_recipients_item.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .monitor_email_recipients_item_invitation_status import MonitorEmailRecipientsItemInvitationStatus +from .monitor_email_recipients_item_status import MonitorEmailRecipientsItemStatus + + +class MonitorEmailRecipientsItem(UniversalBaseModel): + email: str + status: MonitorEmailRecipientsItemStatus + invitation_status: typing_extensions.Annotated[ + typing.Optional[MonitorEmailRecipientsItemInvitationStatus], + FieldMetadata(alias="invitationStatus"), + pydantic.Field(alias="invitationStatus"), + ] = 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/arcmira/types/monitor_email_recipients_item_invitation_status.py b/src/arcmira/types/monitor_email_recipients_item_invitation_status.py new file mode 100644 index 0000000..da1d993 --- /dev/null +++ b/src/arcmira/types/monitor_email_recipients_item_invitation_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MonitorEmailRecipientsItemInvitationStatus = typing.Union[ + typing.Literal["sent", "failed", "limited", "pending"], typing.Any +] diff --git a/src/arcmira/types/monitor_email_recipients_item_status.py b/src/arcmira/types/monitor_email_recipients_item_status.py new file mode 100644 index 0000000..402f68b --- /dev/null +++ b/src/arcmira/types/monitor_email_recipients_item_status.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MonitorEmailRecipientsItemStatus = typing.Union[ + typing.Literal["active", "pending", "unsubscribed", "suppressed", "removed", "owner_unverified", "plan_limited"], + typing.Any, +] diff --git a/src/arcmira/types/monitor_list_response.py b/src/arcmira/types/monitor_list_response.py new file mode 100644 index 0000000..e8f104f --- /dev/null +++ b/src/arcmira/types/monitor_list_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 .monitor_list_response_monitors_item import MonitorListResponseMonitorsItem + + +class MonitorListResponse(UniversalBaseModel): + monitors: typing.List[MonitorListResponseMonitorsItem] = pydantic.Field() + """ + All monitors for the account, ordered by dashboard sort position, then 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/arcmira/types/monitor_list_response_monitors_item.py b/src/arcmira/types/monitor_list_response_monitors_item.py new file mode 100644 index 0000000..ab01c0d --- /dev/null +++ b/src/arcmira/types/monitor_list_response_monitors_item.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.serialization import FieldMetadata +from .monitor import Monitor +from .monitor_list_response_monitors_item_slack_integration import MonitorListResponseMonitorsItemSlackIntegration + + +class MonitorListResponseMonitorsItem(Monitor): + tracker_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="trackerCount"), + pydantic.Field(alias="trackerCount", description="Number of trackers in the monitor."), + ] + """ + Number of trackers in the monitor. + """ + + alerts_this_month: typing_extensions.Annotated[ + int, + FieldMetadata(alias="alertsThisMonth"), + pydantic.Field( + alias="alertsThisMonth", + description="Alert deliveries written for this monitor since the start of the calendar month.", + ), + ] + """ + Alert deliveries written for this monitor since the start of the calendar month. + """ + + slack_integration: typing_extensions.Annotated[ + typing.Optional[MonitorListResponseMonitorsItemSlackIntegration], + FieldMetadata(alias="slackIntegration"), + pydantic.Field( + alias="slackIntegration", + description="Display metadata for the connected Slack integration. Null/absent when Slack is not configured.", + ), + ] = None + """ + Display metadata for the connected Slack integration. Null/absent when Slack is not configured. + """ + + 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/arcmira/types/monitor_list_response_monitors_item_slack_integration.py b/src/arcmira/types/monitor_list_response_monitors_item_slack_integration.py new file mode 100644 index 0000000..e9745bb --- /dev/null +++ b/src/arcmira/types/monitor_list_response_monitors_item_slack_integration.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 MonitorListResponseMonitorsItemSlackIntegration(UniversalBaseModel): + """ + Display metadata for the connected Slack integration. Null/absent when Slack is not configured. + """ + + id: str = pydantic.Field() + """ + Slack integration id. + """ + + team_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Slack workspace name. + """ + + channel_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Slack channel the integration posts to. + """ + + 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/arcmira/types/monitor_mutation_response.py b/src/arcmira/types/monitor_mutation_response.py new file mode 100644 index 0000000..314034c --- /dev/null +++ b/src/arcmira/types/monitor_mutation_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 .monitor_mutation_response_monitor import MonitorMutationResponseMonitor + + +class MonitorMutationResponse(UniversalBaseModel): + monitor: MonitorMutationResponseMonitor + message: str = pydantic.Field() + """ + Human-readable confirmation. + """ + + 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/arcmira/types/monitor_mutation_response_monitor.py b/src/arcmira/types/monitor_mutation_response_monitor.py new file mode 100644 index 0000000..d977c21 --- /dev/null +++ b/src/arcmira/types/monitor_mutation_response_monitor.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.serialization import FieldMetadata +from .monitor import Monitor + + +class MonitorMutationResponseMonitor(Monitor): + tracker_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="trackerCount"), + pydantic.Field( + alias="trackerCount", description="Number of trackers in the monitor. Always 0 in the create response." + ), + ] + """ + Number of trackers in the monitor. Always 0 in the create response. + """ + + webhook_secret: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="webhookSecret"), + pydantic.Field( + alias="webhookSecret", + description='The webhook signing secret ("whsec_..."). Only present when this request NEWLY enabled webhook signing: a create with notifyWebhook: true and a webhookUrl, or a PATCH that turns the webhook on (or sets a URL) where no secret existed before. Store it securely. The same Idempotency-Key can recover it for up to 24 hours while it remains the current or valid previous secret. A displaced or expired secret returns idempotency_result_expired without rotating again.', + ), + ] = None + """ + The webhook signing secret ("whsec_..."). Only present when this request NEWLY enabled webhook signing: a create with notifyWebhook: true and a webhookUrl, or a PATCH that turns the webhook on (or sets a URL) where no secret existed before. Store it securely. The same Idempotency-Key can recover it for up to 24 hours while it remains the current or valid previous secret. A displaced or expired secret returns idempotency_result_expired without rotating again. + """ + + 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/arcmira/types/monitor_trackers_response.py b/src/arcmira/types/monitor_trackers_response.py new file mode 100644 index 0000000..e0976bd --- /dev/null +++ b/src/arcmira/types/monitor_trackers_response.py @@ -0,0 +1,28 @@ +# 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 .monitor_trackers_response_trackers_item import MonitorTrackersResponseTrackersItem + + +class MonitorTrackersResponse(UniversalBaseModel): + trackers: typing.List[MonitorTrackersResponseTrackersItem] = pydantic.Field() + """ + Trackers in the monitor, newest first. + """ + + count: int = pydantic.Field() + """ + Number of trackers returned. + """ + + 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/arcmira/types/monitor_trackers_response_trackers_item.py b/src/arcmira/types/monitor_trackers_response_trackers_item.py new file mode 100644 index 0000000..0ffca3d --- /dev/null +++ b/src/arcmira/types/monitor_trackers_response_trackers_item.py @@ -0,0 +1,105 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class MonitorTrackersResponseTrackersItem(UniversalBaseModel): + id: str = pydantic.Field() + """ + Tracker id. + """ + + entity_name: typing_extensions.Annotated[ + str, FieldMetadata(alias="entityName"), pydantic.Field(alias="entityName", description="Tracked entity name.") + ] + """ + Tracked entity name. + """ + + entity_type: typing_extensions.Annotated[ + str, FieldMetadata(alias="entityType"), pydantic.Field(alias="entityType", description="Tracked entity type.") + ] + """ + Tracked entity type. + """ + + display_name: typing_extensions.Annotated[ + str, + FieldMetadata(alias="displayName"), + pydantic.Field( + alias="displayName", description="User-facing display name. Falls back to entityName when not customized." + ), + ] + """ + User-facing display name. Falls back to entityName when not customized. + """ + + is_paused: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isPaused"), + pydantic.Field(alias="isPaused", description="True when the tracker is paused."), + ] + """ + True when the tracker is paused. + """ + + paused_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="pausedAt"), + pydantic.Field(alias="pausedAt", description="When the tracker was paused. Null unless paused."), + ] = None + """ + When the tracker was paused. Null unless paused. + """ + + last_notified_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="lastNotifiedAt"), + pydantic.Field( + alias="lastNotifiedAt", description="When the tracker last produced an alert. Null until the first alert." + ), + ] = None + """ + When the tracker last produced an alert. Null until the first alert. + """ + + created_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="createdAt"), + pydantic.Field(alias="createdAt", description="When the tracker was created."), + ] + """ + When the tracker was created. + """ + + updated_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="updatedAt"), + pydantic.Field(alias="updatedAt", description="When the tracker was last updated."), + ] = None + """ + When the tracker was last updated. + """ + + monitor_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="monitorId"), + pydantic.Field(alias="monitorId", description="The monitor id from the request path."), + ] + """ + The monitor id from the request path. + """ + + 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/arcmira/types/named_entity_ref.py b/src/arcmira/types/named_entity_ref.py new file mode 100644 index 0000000..81a946c --- /dev/null +++ b/src/arcmira/types/named_entity_ref.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 NamedEntityRef(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public entity id, ent_{n}. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity name. Null when the id no longer resolves. + """ + + type: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + 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/arcmira/types/open_api_document.py b/src/arcmira/types/open_api_document.py new file mode 100644 index 0000000..27b764f --- /dev/null +++ b/src/arcmira/types/open_api_document.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 .open_api_document_info import OpenApiDocumentInfo +from .open_api_document_servers_item import OpenApiDocumentServersItem + + +class OpenApiDocument(UniversalBaseModel): + """ + This OpenAPI 3.1 document, the one you are reading. + """ + + openapi: str = pydantic.Field() + """ + OpenAPI version, 3.1.0. + """ + + info: OpenApiDocumentInfo = pydantic.Field() + """ + API metadata. + """ + + servers: typing.List[OpenApiDocumentServersItem] = pydantic.Field() + """ + Base URLs the API is served from. + """ + + paths: typing.Dict[str, typing.Dict[str, typing.Any]] = pydantic.Field() + """ + Every operation, keyed by path then HTTP method. An open map: each value is an OpenAPI path item object. + """ + + components: typing.Dict[str, typing.Dict[str, typing.Any]] = pydantic.Field() + """ + Shared schemas and security schemes, keyed by component kind then name. An open map of OpenAPI component objects. + """ + + 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/arcmira/types/open_api_document_info.py b/src/arcmira/types/open_api_document_info.py new file mode 100644 index 0000000..62f279e --- /dev/null +++ b/src/arcmira/types/open_api_document_info.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 .open_api_document_info_contact import OpenApiDocumentInfoContact + + +class OpenApiDocumentInfo(UniversalBaseModel): + """ + API metadata. + """ + + title: str = pydantic.Field() + """ + API title. + """ + + version: str = pydantic.Field() + """ + API version. + """ + + contact: OpenApiDocumentInfoContact = pydantic.Field() + """ + Who publishes the API. + """ + + 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/arcmira/types/open_api_document_info_contact.py b/src/arcmira/types/open_api_document_info_contact.py new file mode 100644 index 0000000..a20d2e9 --- /dev/null +++ b/src/arcmira/types/open_api_document_info_contact.py @@ -0,0 +1,31 @@ +# 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 OpenApiDocumentInfoContact(UniversalBaseModel): + """ + Who publishes the API. + """ + + name: str = pydantic.Field() + """ + Contact name. + """ + + url: str = pydantic.Field() + """ + Contact 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/arcmira/types/open_api_document_servers_item.py b/src/arcmira/types/open_api_document_servers_item.py new file mode 100644 index 0000000..639c67e --- /dev/null +++ b/src/arcmira/types/open_api_document_servers_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 OpenApiDocumentServersItem(UniversalBaseModel): + url: str = pydantic.Field() + """ + Base 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/arcmira/types/organization_page_response.py b/src/arcmira/types/organization_page_response.py new file mode 100644 index 0000000..0858ff5 --- /dev/null +++ b/src/arcmira/types/organization_page_response.py @@ -0,0 +1,89 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_page_mention import EntityPageMention +from .exposure_meta import ExposureMeta +from .organization_page_response_channels_item import OrganizationPageResponseChannelsItem +from .organization_page_response_entity import OrganizationPageResponseEntity +from .organization_page_response_mentions_by_month_item import OrganizationPageResponseMentionsByMonthItem +from .organization_page_response_people_item import OrganizationPageResponsePeopleItem +from .organization_page_response_products_item import OrganizationPageResponseProductsItem +from .organization_page_response_role_edge import OrganizationPageResponseRoleEdge +from .organization_page_response_stats import OrganizationPageResponseStats +from .organization_page_response_topics_item import OrganizationPageResponseTopicsItem + + +class OrganizationPageResponse(UniversalBaseModel): + entity: OrganizationPageResponseEntity = pydantic.Field() + """ + The organization and what it owns. + """ + + role_edge: typing_extensions.Annotated[ + typing.Optional[OrganizationPageResponseRoleEdge], + FieldMetadata(alias="roleEdge"), + pydantic.Field( + alias="roleEdge", description="The verified CEO of the organization. Null when none is verified." + ), + ] = None + """ + The verified CEO of the organization. Null when none is verified. + """ + + stats: OrganizationPageResponseStats = pydantic.Field() + """ + Headline numbers and section totals for the organization. + """ + + mentions_by_month: typing_extensions.Annotated[ + typing.List[OrganizationPageResponseMentionsByMonthItem], + FieldMetadata(alias="mentionsByMonth"), + pydantic.Field( + alias="mentionsByMonth", + description="Media mentioning the organization per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart.", + ), + ] + """ + Media mentioning the organization per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart. + """ + + topics: typing.List[OrganizationPageResponseTopicsItem] = pydantic.Field() + """ + Co-occurring topics, highest count first. + """ + + people: typing.List[OrganizationPageResponsePeopleItem] = pydantic.Field() + """ + Co-occurring people, highest count first. + """ + + products: typing.List[OrganizationPageResponseProductsItem] = pydantic.Field() + """ + Products the organization owns that share media with it, most mentions first. + """ + + channels: typing.List[OrganizationPageResponseChannelsItem] = pydantic.Field() + """ + Channels that mention the organization, highest count first. + """ + + mentions: typing.List[EntityPageMention] = pydantic.Field() + """ + Newest media that mention the entity, one row per video, cut to the plan's media rows. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/organization_page_response_channels_item.py b/src/arcmira/types/organization_page_response_channels_item.py new file mode 100644 index 0000000..337b3bc --- /dev/null +++ b/src/arcmira/types/organization_page_response_channels_item.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .organization_page_response_channels_item_sentiment import OrganizationPageResponseChannelsItemSentiment + + +class OrganizationPageResponseChannelsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Channel name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media on this channel that mention the entity. Null when the plan hides counts. + """ + + sentiment: OrganizationPageResponseChannelsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page key on arcmira.com: its @handle when known, else its UC id. Null when neither is known. + """ + + 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/arcmira/types/organization_page_response_channels_item_sentiment.py b/src/arcmira/types/organization_page_response_channels_item_sentiment.py new file mode 100644 index 0000000..488c6f2 --- /dev/null +++ b/src/arcmira/types/organization_page_response_channels_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponseChannelsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/organization_page_response_entity.py b/src/arcmira/types/organization_page_response_entity.py new file mode 100644 index 0000000..ca024cd --- /dev/null +++ b/src/arcmira/types/organization_page_response_entity.py @@ -0,0 +1,99 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .organization_page_response_entity_owned_channels_item import OrganizationPageResponseEntityOwnedChannelsItem +from .organization_page_response_entity_owned_products_item import OrganizationPageResponseEntityOwnedProductsItem +from .organization_page_response_entity_type import OrganizationPageResponseEntityType + + +class OrganizationPageResponseEntity(UniversalBaseModel): + """ + The organization and what it owns. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Organization name. + """ + + type: OrganizationPageResponseEntityType = pydantic.Field() + """ + Always organization. + """ + + category: str = pydantic.Field() + """ + Stored type: organization, company, or brand. + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until resolved."), + ] = None + """ + Logo URL. Null until resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + is_priority: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isPriority"), + pydantic.Field(alias="isPriority", description="True when the entity is flagged priority."), + ] + """ + True when the entity is flagged priority. + """ + + owned_channels: typing_extensions.Annotated[ + typing.Optional[typing.List[OrganizationPageResponseEntityOwnedChannelsItem]], + FieldMetadata(alias="ownedChannels"), + pydantic.Field( + alias="ownedChannels", + description="Up to 10 channels this entity owns, most videos first. Null when it owns none.", + ), + ] = None + """ + Up to 10 channels this entity owns, most videos first. Null when it owns none. + """ + + owned_products: typing_extensions.Annotated[ + typing.Optional[typing.List[OrganizationPageResponseEntityOwnedProductsItem]], + FieldMetadata(alias="ownedProducts"), + pydantic.Field( + alias="ownedProducts", + description="Up to 10 products this entity owns, most mentions first. Null when it owns none.", + ), + ] = None + """ + Up to 10 products this entity owns, most mentions first. Null when it owns 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/arcmira/types/organization_page_response_entity_owned_channels_item.py b/src/arcmira/types/organization_page_response_entity_owned_channels_item.py new file mode 100644 index 0000000..ca1fef9 --- /dev/null +++ b/src/arcmira/types/organization_page_response_entity_owned_channels_item.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class OrganizationPageResponseEntityOwnedChannelsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + display_name: str = pydantic.Field() + """ + Label that tells a homonym apart, e.g. "AdQuick (channel)". Equals name when no label is needed. + """ + + type: str = pydantic.Field() + """ + Entity type: channel or product. + """ + + route: str = pydantic.Field() + """ + Site-relative route of the entity page on arcmira.com. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Entity image URL. Null until resolved."), + ] = None + """ + Entity image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + video_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="videoCount"), + pydantic.Field(alias="videoCount", description="Channels only: media rows published by the channel."), + ] = None + """ + Channels only: media rows published by the channel. + """ + + mention_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="mentionCount"), + pydantic.Field(alias="mentionCount", description="Products only: appearance rows of the product."), + ] = None + """ + Products only: appearance rows of the product. + """ + + 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/arcmira/types/organization_page_response_entity_owned_products_item.py b/src/arcmira/types/organization_page_response_entity_owned_products_item.py new file mode 100644 index 0000000..a523d2a --- /dev/null +++ b/src/arcmira/types/organization_page_response_entity_owned_products_item.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class OrganizationPageResponseEntityOwnedProductsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + display_name: str = pydantic.Field() + """ + Label that tells a homonym apart, e.g. "AdQuick (channel)". Equals name when no label is needed. + """ + + type: str = pydantic.Field() + """ + Entity type: channel or product. + """ + + route: str = pydantic.Field() + """ + Site-relative route of the entity page on arcmira.com. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Entity image URL. Null until resolved."), + ] = None + """ + Entity image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + video_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="videoCount"), + pydantic.Field(alias="videoCount", description="Channels only: media rows published by the channel."), + ] = None + """ + Channels only: media rows published by the channel. + """ + + mention_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="mentionCount"), + pydantic.Field(alias="mentionCount", description="Products only: appearance rows of the product."), + ] = None + """ + Products only: appearance rows of the product. + """ + + 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/arcmira/types/organization_page_response_entity_type.py b/src/arcmira/types/organization_page_response_entity_type.py new file mode 100644 index 0000000..16a4bd9 --- /dev/null +++ b/src/arcmira/types/organization_page_response_entity_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponseEntityType = typing.Union[typing.Literal["organization"], typing.Any] diff --git a/src/arcmira/types/organization_page_response_mentions_by_month_item.py b/src/arcmira/types/organization_page_response_mentions_by_month_item.py new file mode 100644 index 0000000..fd2c490 --- /dev/null +++ b/src/arcmira/types/organization_page_response_mentions_by_month_item.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class OrganizationPageResponseMentionsByMonthItem(UniversalBaseModel): + month: str = pydantic.Field() + """ + Three-letter month name, e.g. Jan. + """ + + year_month: typing_extensions.Annotated[ + str, FieldMetadata(alias="yearMonth"), pydantic.Field(alias="yearMonth", description="The month as YYYY-MM.") + ] + """ + The month as YYYY-MM. + """ + + count: int = pydantic.Field() + """ + Media that month. Months inside the withheld window read 0. + """ + + 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/arcmira/types/organization_page_response_people_item.py b/src/arcmira/types/organization_page_response_people_item.py new file mode 100644 index 0000000..343907b --- /dev/null +++ b/src/arcmira/types/organization_page_response_people_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .organization_page_response_people_item_sentiment import OrganizationPageResponsePeopleItemSentiment + + +class OrganizationPageResponsePeopleItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: OrganizationPageResponsePeopleItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/organization_page_response_people_item_sentiment.py b/src/arcmira/types/organization_page_response_people_item_sentiment.py new file mode 100644 index 0000000..e9e6494 --- /dev/null +++ b/src/arcmira/types/organization_page_response_people_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponsePeopleItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/organization_page_response_products_item.py b/src/arcmira/types/organization_page_response_products_item.py new file mode 100644 index 0000000..06c11a5 --- /dev/null +++ b/src/arcmira/types/organization_page_response_products_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .organization_page_response_products_item_sentiment import OrganizationPageResponseProductsItemSentiment + + +class OrganizationPageResponseProductsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: OrganizationPageResponseProductsItemSentiment = pydantic.Field() + """ + Always neutral. + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/organization_page_response_products_item_sentiment.py b/src/arcmira/types/organization_page_response_products_item_sentiment.py new file mode 100644 index 0000000..b532aa1 --- /dev/null +++ b/src/arcmira/types/organization_page_response_products_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponseProductsItemSentiment = typing.Union[typing.Literal["neutral"], typing.Any] diff --git a/src/arcmira/types/organization_page_response_role_edge.py b/src/arcmira/types/organization_page_response_role_edge.py new file mode 100644 index 0000000..95b1608 --- /dev/null +++ b/src/arcmira/types/organization_page_response_role_edge.py @@ -0,0 +1,137 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .organization_page_response_role_edge_label import OrganizationPageResponseRoleEdgeLabel +from .organization_page_response_role_edge_people_item import OrganizationPageResponseRoleEdgePeopleItem +from .organization_page_response_role_edge_recent_appearances_item import ( + OrganizationPageResponseRoleEdgeRecentAppearancesItem, +) +from .organization_page_response_role_edge_role import OrganizationPageResponseRoleEdgeRole + + +class OrganizationPageResponseRoleEdge(UniversalBaseModel): + """ + The verified CEO of the organization. Null when none is verified. + """ + + role: OrganizationPageResponseRoleEdgeRole = pydantic.Field() + """ + Always ceo. + """ + + label: OrganizationPageResponseRoleEdgeLabel = pydantic.Field() + """ + CO-CEOS when operator-certified co-CEOs lead the organization, CEO otherwise. + """ + + name: str = pydantic.Field() + """ + CEO name, or the co-CEO names joined with "and". + """ + + href: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative link to the CEO page. Null for co-CEOs (see people) or when none. + """ + + receipt: typing.Optional[str] = pydantic.Field(default=None) + """ + Display line naming the evidence for the primary CEO. Null when there is none to show. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Primary CEO image URL. Null until resolved."), + ] = None + """ + Primary CEO image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", + description="When the image pipeline last checked the primary CEO. Null until checked.", + ), + ] = None + """ + When the image pipeline last checked the primary CEO. Null until checked. + """ + + people: typing.Optional[typing.List[OrganizationPageResponseRoleEdgePeopleItem]] = pydantic.Field(default=None) + """ + Co-CEOs only: one entry per co-CEO. Absent for a single CEO. + """ + + confirmations: int = pydantic.Field() + """ + Most confirmations of the role across the CEO edges. + """ + + first_confirmed_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="firstConfirmedAt"), + pydantic.Field( + alias="firstConfirmedAt", description="When the primary CEO role was first confirmed. Null when unknown." + ), + ] = None + """ + When the primary CEO role was first confirmed. Null when unknown. + """ + + last_confirmed_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="lastConfirmedAt"), + pydantic.Field( + alias="lastConfirmedAt", description="When the primary CEO role was last confirmed. Null when unknown." + ), + ] = None + """ + When the primary CEO role was last confirmed. Null when unknown. + """ + + verified_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="verifiedAt"), + pydantic.Field(alias="verifiedAt", description="When the primary CEO role was verified."), + ] + """ + When the primary CEO role was verified. + """ + + true_appearances: typing_extensions.Annotated[ + int, + FieldMetadata(alias="trueAppearances"), + pydantic.Field(alias="trueAppearances", description="Most media any of the CEOs appeared in."), + ] + """ + Most media any of the CEOs appeared in. + """ + + recent_appearances: typing_extensions.Annotated[ + typing.List[OrganizationPageResponseRoleEdgeRecentAppearancesItem], + FieldMetadata(alias="recentAppearances"), + pydantic.Field( + alias="recentAppearances", + description="Newest media the CEO appeared in, outside the withheld window. Empty when no CEO has enough appearances to list.", + ), + ] + """ + Newest media the CEO appeared in, outside the withheld window. Empty when no CEO has enough appearances to list. + """ + + 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/arcmira/types/organization_page_response_role_edge_label.py b/src/arcmira/types/organization_page_response_role_edge_label.py new file mode 100644 index 0000000..2bbb404 --- /dev/null +++ b/src/arcmira/types/organization_page_response_role_edge_label.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponseRoleEdgeLabel = typing.Union[typing.Literal["CEO", "CO-CEOS"], typing.Any] diff --git a/src/arcmira/types/organization_page_response_role_edge_people_item.py b/src/arcmira/types/organization_page_response_role_edge_people_item.py new file mode 100644 index 0000000..24d022e --- /dev/null +++ b/src/arcmira/types/organization_page_response_role_edge_people_item.py @@ -0,0 +1,63 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class OrganizationPageResponseRoleEdgePeopleItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Co-CEO name. + """ + + href: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative link to the co-CEO page. Null when none. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Co-CEO image URL. Null until resolved."), + ] = None + """ + Co-CEO image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this person. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this person. Null until checked. + """ + + true_appearances: typing_extensions.Annotated[ + int, + FieldMetadata(alias="trueAppearances"), + pydantic.Field(alias="trueAppearances", description="Media the co-CEO appeared in."), + ] + """ + Media the co-CEO appeared in. + """ + + receipt: typing.Optional[str] = pydantic.Field(default=None) + """ + Display line naming the evidence. Null when there is none to show. + """ + + 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/arcmira/types/organization_page_response_role_edge_recent_appearances_item.py b/src/arcmira/types/organization_page_response_role_edge_recent_appearances_item.py new file mode 100644 index 0000000..38bab0e --- /dev/null +++ b/src/arcmira/types/organization_page_response_role_edge_recent_appearances_item.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class OrganizationPageResponseRoleEdgeRecentAppearancesItem(UniversalBaseModel): + title: str = pydantic.Field() + """ + Video title. + """ + + published_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish timestamp. Null when unknown."), + ] = None + """ + Publish timestamp. Null when unknown. + """ + + video_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id. Null when unknown."), + ] = None + """ + YouTube video id. Null when unknown. + """ + + channel: typing.Optional[str] = pydantic.Field(default=None) + """ + Source channel name. Null when unknown. + """ + + channel_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelHandle"), + pydantic.Field(alias="channelHandle", description="YouTube handle of the source channel. Null when unknown."), + ] = None + """ + YouTube handle of the source channel. Null when unknown. + """ + + timestamp: typing.Optional[str] = pydantic.Field(default=None) + """ + Appearance start as MM:SS text. Null when unknown. + """ + + 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/arcmira/types/organization_page_response_role_edge_role.py b/src/arcmira/types/organization_page_response_role_edge_role.py new file mode 100644 index 0000000..a158b85 --- /dev/null +++ b/src/arcmira/types/organization_page_response_role_edge_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponseRoleEdgeRole = typing.Union[typing.Literal["ceo"], typing.Any] diff --git a/src/arcmira/types/organization_page_response_stats.py b/src/arcmira/types/organization_page_response_stats.py new file mode 100644 index 0000000..460e40b --- /dev/null +++ b/src/arcmira/types/organization_page_response_stats.py @@ -0,0 +1,84 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class OrganizationPageResponseStats(UniversalBaseModel): + """ + Headline numbers and section totals for the organization. + """ + + velocity: typing.Optional[int] = pydantic.Field(default=None) + """ + Mentions in the last 90 days. Null when the plan hides it. + """ + + sentiment: float = pydantic.Field() + """ + Reserved. Always 0. + """ + + reach: str = pydantic.Field() + """ + Total views as display text, e.g. 1.2M. + """ + + reach_raw: typing_extensions.Annotated[ + float, + FieldMetadata(alias="reachRaw"), + pydantic.Field(alias="reachRaw", description="Total views of the media that mention the organization."), + ] + """ + Total views of the media that mention the organization. + """ + + total: int = pydantic.Field() + """ + Total media that mention the organization. + """ + + latest_media_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="latestMediaAt"), + pydantic.Field( + alias="latestMediaAt", + description="Newest publish date among the counted media. Null when none. The freshness gate does not withhold it.", + ), + ] = None + """ + Newest publish date among the counted media. Null when none. The freshness gate does not withhold it. + """ + + people: int = pydantic.Field() + """ + Total co-occurring people. + """ + + topics: int = pydantic.Field() + """ + Total co-occurring topics. + """ + + products: int = pydantic.Field() + """ + Owned products with mentions. + """ + + channels: int = pydantic.Field() + """ + Total channels that mention the organization. + """ + + 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/arcmira/types/organization_page_response_topics_item.py b/src/arcmira/types/organization_page_response_topics_item.py new file mode 100644 index 0000000..1c7574f --- /dev/null +++ b/src/arcmira/types/organization_page_response_topics_item.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 .organization_page_response_topics_item_sentiment import OrganizationPageResponseTopicsItemSentiment + + +class OrganizationPageResponseTopicsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Topic name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: OrganizationPageResponseTopicsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + 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/arcmira/types/organization_page_response_topics_item_sentiment.py b/src/arcmira/types/organization_page_response_topics_item_sentiment.py new file mode 100644 index 0000000..4aaa2c5 --- /dev/null +++ b/src/arcmira/types/organization_page_response_topics_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OrganizationPageResponseTopicsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/person_appearance_list_response.py b/src/arcmira/types/person_appearance_list_response.py new file mode 100644 index 0000000..1b1dc35 --- /dev/null +++ b/src/arcmira/types/person_appearance_list_response.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta import ExposureMeta +from .person_appearance_list_response_items_item import PersonAppearanceListResponseItemsItem + + +class PersonAppearanceListResponse(UniversalBaseModel): + items: typing.List[PersonAppearanceListResponseItemsItem] = pydantic.Field() + """ + Media the person appeared in, newest first by default, one row per video. + """ + + total: int = pydantic.Field() + """ + Rows matching the filter across all pages. + """ + + offset: int = pydantic.Field() + """ + Row offset of this page, as the cursor encoded it. 0 on the first page. + """ + + limit: int = pydantic.Field() + """ + Page size applied, after the plan clamp. + """ + + has_more: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="hasMore"), + pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), + ] + """ + Same value as has_more, kept for readers of the web shape. + """ + + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/person_appearance_list_response_items_item.py b/src/arcmira/types/person_appearance_list_response_items_item.py new file mode 100644 index 0000000..b8ca1bb --- /dev/null +++ b/src/arcmira/types/person_appearance_list_response_items_item.py @@ -0,0 +1,134 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_appearance_list_response_items_item_platform import PersonAppearanceListResponseItemsItemPlatform +from .person_appearance_list_response_items_item_sentiment import PersonAppearanceListResponseItemsItemSentiment +from .person_appearance_list_response_items_item_type import PersonAppearanceListResponseItemsItemType +from .published_excerpt import PublishedExcerpt + + +class PersonAppearanceListResponseItemsItem(UniversalBaseModel): + id: str = pydantic.Field() + """ + Raw appearance row id (the first row for the video), as a string. + """ + + date: str = pydantic.Field() + """ + Publish date as locale display text, or Unknown. + """ + + date_raw: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="dateRaw"), + pydantic.Field(alias="dateRaw", description="Publish timestamp. Null when unknown."), + ] = None + """ + Publish timestamp. Null when unknown. + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + channel: str = pydantic.Field() + """ + Source channel name, or Unknown Channel. + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field(alias="channelId", description="YouTube channel id of the source channel. Null when unknown."), + ] = None + """ + YouTube channel id of the source channel. Null when unknown. + """ + + channel_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelHandle"), + pydantic.Field(alias="channelHandle", description="YouTube handle of the source channel. Null when unknown."), + ] = None + """ + YouTube handle of the source channel. Null when unknown. + """ + + platform: PersonAppearanceListResponseItemsItemPlatform = pydantic.Field() + """ + Always youtube. + """ + + type: PersonAppearanceListResponseItemsItemType = pydantic.Field() + """ + host when the person hosts the channel, guest otherwise. mention only when the request passed is_appearance=false, which lists media that mention the person instead. + """ + + context: str = pydantic.Field() + """ + Longest description of the appearance, or "No description available". + """ + + sentiment: PersonAppearanceListResponseItemsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + thumbnail: str = pydantic.Field() + """ + Video thumbnail URL. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + timestamp: str = pydantic.Field() + """ + Earliest start timestamp as MM:SS text, or "Full Episode" when none. + """ + + raw_timestamp: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="rawTimestamp"), + pydantic.Field(alias="rawTimestamp", description="Earliest start timestamp as stored. Null when none."), + ] = None + """ + Earliest start timestamp as stored. Null when none. + """ + + duration: typing.Optional[float] = pydantic.Field(default=None) + """ + Video length in seconds. Null when unknown. + """ + + view_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="viewCount"), + pydantic.Field(alias="viewCount", description="Video view count at index time. Null when never fetched."), + ] = None + """ + Video view count at index time. Null when never fetched. + """ + + excerpt: typing.Optional[PublishedExcerpt] = 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/arcmira/types/person_appearance_list_response_items_item_platform.py b/src/arcmira/types/person_appearance_list_response_items_item_platform.py new file mode 100644 index 0000000..5c740f8 --- /dev/null +++ b/src/arcmira/types/person_appearance_list_response_items_item_platform.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonAppearanceListResponseItemsItemPlatform = typing.Union[typing.Literal["youtube"], typing.Any] diff --git a/src/arcmira/types/person_appearance_list_response_items_item_sentiment.py b/src/arcmira/types/person_appearance_list_response_items_item_sentiment.py new file mode 100644 index 0000000..7d03959 --- /dev/null +++ b/src/arcmira/types/person_appearance_list_response_items_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonAppearanceListResponseItemsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/person_appearance_list_response_items_item_type.py b/src/arcmira/types/person_appearance_list_response_items_item_type.py new file mode 100644 index 0000000..c1eb7cf --- /dev/null +++ b/src/arcmira/types/person_appearance_list_response_items_item_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonAppearanceListResponseItemsItemType = typing.Union[typing.Literal["host", "guest", "mention"], typing.Any] diff --git a/src/arcmira/types/person_page_response.py b/src/arcmira/types/person_page_response.py new file mode 100644 index 0000000..eb8c370 --- /dev/null +++ b/src/arcmira/types/person_page_response.py @@ -0,0 +1,106 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .exposure_meta import ExposureMeta +from .person_page_response_appearances_by_month_item import PersonPageResponseAppearancesByMonthItem +from .person_page_response_appearances_item import PersonPageResponseAppearancesItem +from .person_page_response_brands_item import PersonPageResponseBrandsItem +from .person_page_response_entity import PersonPageResponseEntity +from .person_page_response_mentions_by_month_item import PersonPageResponseMentionsByMonthItem +from .person_page_response_mentions_item import PersonPageResponseMentionsItem +from .person_page_response_people_item import PersonPageResponsePeopleItem +from .person_page_response_products_item import PersonPageResponseProductsItem +from .person_page_response_role_edge import PersonPageResponseRoleEdge +from .person_page_response_stats import PersonPageResponseStats +from .person_page_response_topics_item import PersonPageResponseTopicsItem + + +class PersonPageResponse(UniversalBaseModel): + entity: PersonPageResponseEntity = pydantic.Field() + """ + The person: the stored entity row plus camelCase image fields and what the person owns. + """ + + role_edge: typing_extensions.Annotated[ + typing.Optional[PersonPageResponseRoleEdge], + FieldMetadata(alias="roleEdge"), + pydantic.Field(alias="roleEdge", description="A verified CEO role for the person. Null when none is verified."), + ] = None + """ + A verified CEO role for the person. Null when none is verified. + """ + + stats: PersonPageResponseStats = pydantic.Field() + """ + Headline numbers for the person. + """ + + appearances_by_month: typing_extensions.Annotated[ + typing.List[PersonPageResponseAppearancesByMonthItem], + FieldMetadata(alias="appearancesByMonth"), + pydantic.Field( + alias="appearancesByMonth", + description="Appearances per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart.", + ), + ] + """ + Appearances per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart. + """ + + mentions_by_month: typing_extensions.Annotated[ + typing.List[PersonPageResponseMentionsByMonthItem], + FieldMetadata(alias="mentionsByMonth"), + pydantic.Field( + alias="mentionsByMonth", + description="Media mentioning the person per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart.", + ), + ] + """ + Media mentioning the person per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart. + """ + + topics: typing.List[PersonPageResponseTopicsItem] = pydantic.Field() + """ + Topics in the media the person appeared in, highest count first. + """ + + people: typing.List[PersonPageResponsePeopleItem] = pydantic.Field() + """ + People in the media the person appeared in, highest count first. + """ + + brands: typing.List[PersonPageResponseBrandsItem] = pydantic.Field() + """ + Organizations in the media the person appeared in, highest count first. + """ + + products: typing.List[PersonPageResponseProductsItem] = pydantic.Field() + """ + Products in the media the person appeared in, highest count first. + """ + + appearances: typing.List[PersonPageResponseAppearancesItem] = pydantic.Field() + """ + Newest media the person appeared in, one row per video, cut to the plan's media rows. + """ + + mentions: typing.List[PersonPageResponseMentionsItem] = pydantic.Field() + """ + Newest media that mention the person without them appearing, one row per video. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/person_page_response_appearances_by_month_item.py b/src/arcmira/types/person_page_response_appearances_by_month_item.py new file mode 100644 index 0000000..025e6df --- /dev/null +++ b/src/arcmira/types/person_page_response_appearances_by_month_item.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PersonPageResponseAppearancesByMonthItem(UniversalBaseModel): + month: str = pydantic.Field() + """ + Three-letter month name, e.g. Jan. + """ + + year_month: typing_extensions.Annotated[ + str, FieldMetadata(alias="yearMonth"), pydantic.Field(alias="yearMonth", description="The month as YYYY-MM.") + ] + """ + The month as YYYY-MM. + """ + + count: int = pydantic.Field() + """ + Media that month. Months inside the withheld window read 0. + """ + + 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/arcmira/types/person_page_response_appearances_item.py b/src/arcmira/types/person_page_response_appearances_item.py new file mode 100644 index 0000000..ec82cad --- /dev/null +++ b/src/arcmira/types/person_page_response_appearances_item.py @@ -0,0 +1,134 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_appearances_item_platform import PersonPageResponseAppearancesItemPlatform +from .person_page_response_appearances_item_sentiment import PersonPageResponseAppearancesItemSentiment +from .person_page_response_appearances_item_type import PersonPageResponseAppearancesItemType +from .published_excerpt import PublishedExcerpt + + +class PersonPageResponseAppearancesItem(UniversalBaseModel): + id: str = pydantic.Field() + """ + Raw appearance row id (the first row for the video), as a string. + """ + + date: str = pydantic.Field() + """ + Publish date as locale display text, or Unknown. + """ + + published_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish timestamp. Null when unknown."), + ] = None + """ + Publish timestamp. Null when unknown. + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + channel: str = pydantic.Field() + """ + Source channel name, or Unknown Channel. + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field(alias="channelId", description="YouTube channel id of the source channel. Null when unknown."), + ] = None + """ + YouTube channel id of the source channel. Null when unknown. + """ + + channel_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelHandle"), + pydantic.Field(alias="channelHandle", description="YouTube handle of the source channel. Null when unknown."), + ] = None + """ + YouTube handle of the source channel. Null when unknown. + """ + + platform: PersonPageResponseAppearancesItemPlatform = pydantic.Field() + """ + Always youtube. + """ + + thumbnail: str = pydantic.Field() + """ + Video thumbnail URL. + """ + + thumbnail_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="thumbnailUrl"), + pydantic.Field(alias="thumbnailUrl", description="Video thumbnail URL. Same value as thumbnail."), + ] + """ + Video thumbnail URL. Same value as thumbnail. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + duration: typing.Optional[float] = pydantic.Field(default=None) + """ + Video length in seconds. Null when unknown. + """ + + type: PersonPageResponseAppearancesItemType = pydantic.Field() + """ + host or guest on an appearance row (host when the person hosts the channel); mention on a mention row. + """ + + context: str = pydantic.Field() + """ + Description of the moment, or "No description available". + """ + + sentiment: PersonPageResponseAppearancesItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + timestamp: str = pydantic.Field() + """ + Earliest start timestamp as MM:SS text, or "Full Episode" when none. + """ + + raw_timestamp: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="rawTimestamp"), + pydantic.Field(alias="rawTimestamp", description="Earliest start timestamp as stored. Null when none."), + ] = None + """ + Earliest start timestamp as stored. Null when none. + """ + + excerpt: typing.Optional[PublishedExcerpt] = 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/arcmira/types/person_page_response_appearances_item_platform.py b/src/arcmira/types/person_page_response_appearances_item_platform.py new file mode 100644 index 0000000..c7fd173 --- /dev/null +++ b/src/arcmira/types/person_page_response_appearances_item_platform.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseAppearancesItemPlatform = typing.Union[typing.Literal["youtube"], typing.Any] diff --git a/src/arcmira/types/person_page_response_appearances_item_sentiment.py b/src/arcmira/types/person_page_response_appearances_item_sentiment.py new file mode 100644 index 0000000..9412cdb --- /dev/null +++ b/src/arcmira/types/person_page_response_appearances_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseAppearancesItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/person_page_response_appearances_item_type.py b/src/arcmira/types/person_page_response_appearances_item_type.py new file mode 100644 index 0000000..a5d58dc --- /dev/null +++ b/src/arcmira/types/person_page_response_appearances_item_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseAppearancesItemType = typing.Union[typing.Literal["host", "guest", "mention"], typing.Any] diff --git a/src/arcmira/types/person_page_response_brands_item.py b/src/arcmira/types/person_page_response_brands_item.py new file mode 100644 index 0000000..fb1fd36 --- /dev/null +++ b/src/arcmira/types/person_page_response_brands_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_brands_item_sentiment import PersonPageResponseBrandsItemSentiment + + +class PersonPageResponseBrandsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: PersonPageResponseBrandsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/person_page_response_brands_item_sentiment.py b/src/arcmira/types/person_page_response_brands_item_sentiment.py new file mode 100644 index 0000000..385d1d9 --- /dev/null +++ b/src/arcmira/types/person_page_response_brands_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseBrandsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/person_page_response_entity.py b/src/arcmira/types/person_page_response_entity.py new file mode 100644 index 0000000..d12caa1 --- /dev/null +++ b/src/arcmira/types/person_page_response_entity.py @@ -0,0 +1,132 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_entity_owned_channels_item import PersonPageResponseEntityOwnedChannelsItem +from .person_page_response_entity_owned_products_item import PersonPageResponseEntityOwnedProductsItem + + +class PersonPageResponseEntity(UniversalBaseModel): + """ + The person: the stored entity row plus camelCase image fields and what the person owns. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Person name. + """ + + type: str = pydantic.Field() + """ + Stored entity type. Always person. + """ + + platform: typing.Optional[str] = pydantic.Field(default=None) + """ + Source platform. Null for people. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + External URL for the person. Null when none is known. + """ + + created_at: str = pydantic.Field() + """ + When the entity row was created. + """ + + image_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Person image URL. Null until resolved. + """ + + image_checked_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the image pipeline last checked this entity. Null until checked. + """ + + merged_into_entity_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Raw id of the entity this one was merged into. Null on a canonical entity, which a page always serves. + """ + + owner_entity_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Raw id of the owning entity. Null unless an ownership link exists. + """ + + youtube_channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id. Null for people. + """ + + youtube_handle: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube handle. Null for people. + """ + + is_priority: typing.Optional[int] = pydantic.Field(default=None) + """ + 1 when the entity is flagged priority, 0 or null otherwise. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Same value as image_url."), + ] = None + """ + Same value as image_url. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field(alias="imageCheckedAt", description="Same value as image_checked_at."), + ] = None + """ + Same value as image_checked_at. + """ + + owned_channels: typing_extensions.Annotated[ + typing.Optional[typing.List[PersonPageResponseEntityOwnedChannelsItem]], + FieldMetadata(alias="ownedChannels"), + pydantic.Field( + alias="ownedChannels", + description="Up to 10 channels this entity owns, most videos first. Null when it owns none.", + ), + ] = None + """ + Up to 10 channels this entity owns, most videos first. Null when it owns none. + """ + + owned_products: typing_extensions.Annotated[ + typing.Optional[typing.List[PersonPageResponseEntityOwnedProductsItem]], + FieldMetadata(alias="ownedProducts"), + pydantic.Field( + alias="ownedProducts", + description="Up to 10 products this entity owns, most mentions first. Null when it owns none.", + ), + ] = None + """ + Up to 10 products this entity owns, most mentions first. Null when it owns 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/arcmira/types/person_page_response_entity_owned_channels_item.py b/src/arcmira/types/person_page_response_entity_owned_channels_item.py new file mode 100644 index 0000000..13a5b49 --- /dev/null +++ b/src/arcmira/types/person_page_response_entity_owned_channels_item.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PersonPageResponseEntityOwnedChannelsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + display_name: str = pydantic.Field() + """ + Label that tells a homonym apart, e.g. "AdQuick (channel)". Equals name when no label is needed. + """ + + type: str = pydantic.Field() + """ + Entity type: channel or product. + """ + + route: str = pydantic.Field() + """ + Site-relative route of the entity page on arcmira.com. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Entity image URL. Null until resolved."), + ] = None + """ + Entity image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + video_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="videoCount"), + pydantic.Field(alias="videoCount", description="Channels only: media rows published by the channel."), + ] = None + """ + Channels only: media rows published by the channel. + """ + + mention_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="mentionCount"), + pydantic.Field(alias="mentionCount", description="Products only: appearance rows of the product."), + ] = None + """ + Products only: appearance rows of the product. + """ + + 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/arcmira/types/person_page_response_entity_owned_products_item.py b/src/arcmira/types/person_page_response_entity_owned_products_item.py new file mode 100644 index 0000000..d4b5698 --- /dev/null +++ b/src/arcmira/types/person_page_response_entity_owned_products_item.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PersonPageResponseEntityOwnedProductsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + display_name: str = pydantic.Field() + """ + Label that tells a homonym apart, e.g. "AdQuick (channel)". Equals name when no label is needed. + """ + + type: str = pydantic.Field() + """ + Entity type: channel or product. + """ + + route: str = pydantic.Field() + """ + Site-relative route of the entity page on arcmira.com. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Entity image URL. Null until resolved."), + ] = None + """ + Entity image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + video_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="videoCount"), + pydantic.Field(alias="videoCount", description="Channels only: media rows published by the channel."), + ] = None + """ + Channels only: media rows published by the channel. + """ + + mention_count: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="mentionCount"), + pydantic.Field(alias="mentionCount", description="Products only: appearance rows of the product."), + ] = None + """ + Products only: appearance rows of the product. + """ + + 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/arcmira/types/person_page_response_mentions_by_month_item.py b/src/arcmira/types/person_page_response_mentions_by_month_item.py new file mode 100644 index 0000000..9f01e6a --- /dev/null +++ b/src/arcmira/types/person_page_response_mentions_by_month_item.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PersonPageResponseMentionsByMonthItem(UniversalBaseModel): + month: str = pydantic.Field() + """ + Three-letter month name, e.g. Jan. + """ + + year_month: typing_extensions.Annotated[ + str, FieldMetadata(alias="yearMonth"), pydantic.Field(alias="yearMonth", description="The month as YYYY-MM.") + ] + """ + The month as YYYY-MM. + """ + + count: int = pydantic.Field() + """ + Media that month. Months inside the withheld window read 0. + """ + + 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/arcmira/types/person_page_response_mentions_item.py b/src/arcmira/types/person_page_response_mentions_item.py new file mode 100644 index 0000000..9bc6859 --- /dev/null +++ b/src/arcmira/types/person_page_response_mentions_item.py @@ -0,0 +1,134 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_mentions_item_platform import PersonPageResponseMentionsItemPlatform +from .person_page_response_mentions_item_sentiment import PersonPageResponseMentionsItemSentiment +from .person_page_response_mentions_item_type import PersonPageResponseMentionsItemType +from .published_excerpt import PublishedExcerpt + + +class PersonPageResponseMentionsItem(UniversalBaseModel): + id: str = pydantic.Field() + """ + Raw appearance row id (the first row for the video), as a string. + """ + + date: str = pydantic.Field() + """ + Publish date as locale display text, or Unknown. + """ + + published_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Publish timestamp. Null when unknown."), + ] = None + """ + Publish timestamp. Null when unknown. + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + channel: str = pydantic.Field() + """ + Source channel name, or Unknown Channel. + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field(alias="channelId", description="YouTube channel id of the source channel. Null when unknown."), + ] = None + """ + YouTube channel id of the source channel. Null when unknown. + """ + + channel_handle: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelHandle"), + pydantic.Field(alias="channelHandle", description="YouTube handle of the source channel. Null when unknown."), + ] = None + """ + YouTube handle of the source channel. Null when unknown. + """ + + platform: PersonPageResponseMentionsItemPlatform = pydantic.Field() + """ + Always youtube. + """ + + thumbnail: str = pydantic.Field() + """ + Video thumbnail URL. + """ + + thumbnail_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="thumbnailUrl"), + pydantic.Field(alias="thumbnailUrl", description="Video thumbnail URL. Same value as thumbnail."), + ] + """ + Video thumbnail URL. Same value as thumbnail. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + duration: typing.Optional[float] = pydantic.Field(default=None) + """ + Video length in seconds. Null when unknown. + """ + + type: PersonPageResponseMentionsItemType = pydantic.Field() + """ + host or guest on an appearance row (host when the person hosts the channel); mention on a mention row. + """ + + context: str = pydantic.Field() + """ + Description of the moment, or "No description available". + """ + + sentiment: PersonPageResponseMentionsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + timestamp: str = pydantic.Field() + """ + Earliest start timestamp as MM:SS text, or "Full Episode" when none. + """ + + raw_timestamp: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="rawTimestamp"), + pydantic.Field(alias="rawTimestamp", description="Earliest start timestamp as stored. Null when none."), + ] = None + """ + Earliest start timestamp as stored. Null when none. + """ + + excerpt: typing.Optional[PublishedExcerpt] = 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/arcmira/types/person_page_response_mentions_item_platform.py b/src/arcmira/types/person_page_response_mentions_item_platform.py new file mode 100644 index 0000000..2c9bfef --- /dev/null +++ b/src/arcmira/types/person_page_response_mentions_item_platform.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseMentionsItemPlatform = typing.Union[typing.Literal["youtube"], typing.Any] diff --git a/src/arcmira/types/person_page_response_mentions_item_sentiment.py b/src/arcmira/types/person_page_response_mentions_item_sentiment.py new file mode 100644 index 0000000..5debc83 --- /dev/null +++ b/src/arcmira/types/person_page_response_mentions_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseMentionsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/person_page_response_mentions_item_type.py b/src/arcmira/types/person_page_response_mentions_item_type.py new file mode 100644 index 0000000..53b5cfe --- /dev/null +++ b/src/arcmira/types/person_page_response_mentions_item_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseMentionsItemType = typing.Union[typing.Literal["host", "guest", "mention"], typing.Any] diff --git a/src/arcmira/types/person_page_response_people_item.py b/src/arcmira/types/person_page_response_people_item.py new file mode 100644 index 0000000..1405ea6 --- /dev/null +++ b/src/arcmira/types/person_page_response_people_item.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_people_item_role import PersonPageResponsePeopleItemRole +from .person_page_response_people_item_sentiment import PersonPageResponsePeopleItemSentiment + + +class PersonPageResponsePeopleItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: PersonPageResponsePeopleItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + role: PersonPageResponsePeopleItemRole = pydantic.Field() + """ + Always Connection. + """ + + 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/arcmira/types/person_page_response_people_item_role.py b/src/arcmira/types/person_page_response_people_item_role.py new file mode 100644 index 0000000..5556b0e --- /dev/null +++ b/src/arcmira/types/person_page_response_people_item_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponsePeopleItemRole = typing.Union[typing.Literal["Connection"], typing.Any] diff --git a/src/arcmira/types/person_page_response_people_item_sentiment.py b/src/arcmira/types/person_page_response_people_item_sentiment.py new file mode 100644 index 0000000..1d155a2 --- /dev/null +++ b/src/arcmira/types/person_page_response_people_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponsePeopleItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/person_page_response_products_item.py b/src/arcmira/types/person_page_response_products_item.py new file mode 100644 index 0000000..6360415 --- /dev/null +++ b/src/arcmira/types/person_page_response_products_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_products_item_sentiment import PersonPageResponseProductsItemSentiment + + +class PersonPageResponseProductsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: PersonPageResponseProductsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/person_page_response_products_item_sentiment.py b/src/arcmira/types/person_page_response_products_item_sentiment.py new file mode 100644 index 0000000..5c23163 --- /dev/null +++ b/src/arcmira/types/person_page_response_products_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseProductsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/person_page_response_role_edge.py b/src/arcmira/types/person_page_response_role_edge.py new file mode 100644 index 0000000..6a7c2d5 --- /dev/null +++ b/src/arcmira/types/person_page_response_role_edge.py @@ -0,0 +1,71 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .person_page_response_role_edge_label import PersonPageResponseRoleEdgeLabel +from .person_page_response_role_edge_role import PersonPageResponseRoleEdgeRole + + +class PersonPageResponseRoleEdge(UniversalBaseModel): + """ + A verified CEO role for the person. Null when none is verified. + """ + + role: PersonPageResponseRoleEdgeRole = pydantic.Field() + """ + Always ceo. + """ + + label: PersonPageResponseRoleEdgeLabel = pydantic.Field() + """ + Display label for the role line. + """ + + name: str = pydantic.Field() + """ + Name of the organization the person leads. + """ + + href: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative link to the organization page. Null when none. + """ + + receipt: typing.Optional[str] = pydantic.Field(default=None) + """ + Display line naming the evidence for the role. Null when there is none to show. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Organization logo URL. Null until resolved."), + ] = None + """ + Organization logo URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", + description="When the image pipeline last checked the organization. Null until checked.", + ), + ] = None + """ + When the image pipeline last checked the organization. Null until checked. + """ + + 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/arcmira/types/person_page_response_role_edge_label.py b/src/arcmira/types/person_page_response_role_edge_label.py new file mode 100644 index 0000000..3074b22 --- /dev/null +++ b/src/arcmira/types/person_page_response_role_edge_label.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseRoleEdgeLabel = typing.Union[typing.Literal["CEO of", "CO-CEO of"], typing.Any] diff --git a/src/arcmira/types/person_page_response_role_edge_role.py b/src/arcmira/types/person_page_response_role_edge_role.py new file mode 100644 index 0000000..87290b8 --- /dev/null +++ b/src/arcmira/types/person_page_response_role_edge_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseRoleEdgeRole = typing.Union[typing.Literal["ceo"], typing.Any] diff --git a/src/arcmira/types/person_page_response_stats.py b/src/arcmira/types/person_page_response_stats.py new file mode 100644 index 0000000..e595a0f --- /dev/null +++ b/src/arcmira/types/person_page_response_stats.py @@ -0,0 +1,91 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PersonPageResponseStats(UniversalBaseModel): + """ + Headline numbers for the person. + """ + + velocity: int = pydantic.Field() + """ + Appearances in the last 90 days. + """ + + sentiment: float = pydantic.Field() + """ + Reserved. Always 0. + """ + + reach: str = pydantic.Field() + """ + Total views of the person's appearances as display text, e.g. 1.2M. + """ + + reach_raw: typing_extensions.Annotated[ + float, + FieldMetadata(alias="reachRaw"), + pydantic.Field(alias="reachRaw", description="Total views of the person's appearances."), + ] + """ + Total views of the person's appearances. + """ + + total: int = pydantic.Field() + """ + Total appearances. + """ + + total_mentions: typing_extensions.Annotated[ + int, + FieldMetadata(alias="totalMentions"), + pydantic.Field(alias="totalMentions", description="Total media that mention the person."), + ] + """ + Total media that mention the person. + """ + + total_mention_views: typing_extensions.Annotated[ + float, + FieldMetadata(alias="totalMentionViews"), + pydantic.Field(alias="totalMentionViews", description="Total views of the media that mention the person."), + ] + """ + Total views of the media that mention the person. + """ + + mention_reach: typing_extensions.Annotated[ + str, + FieldMetadata(alias="mentionReach"), + pydantic.Field(alias="mentionReach", description="totalMentionViews as display text."), + ] + """ + totalMentionViews as display text. + """ + + latest_media_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="latestMediaAt"), + pydantic.Field( + alias="latestMediaAt", + description="Newest publish date among the counted media. Null when none. The freshness gate does not withhold it.", + ), + ] = None + """ + Newest publish date among the counted media. Null when none. The freshness gate does not withhold it. + """ + + 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/arcmira/types/person_page_response_topics_item.py b/src/arcmira/types/person_page_response_topics_item.py new file mode 100644 index 0000000..a854d8e --- /dev/null +++ b/src/arcmira/types/person_page_response_topics_item.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 .person_page_response_topics_item_sentiment import PersonPageResponseTopicsItemSentiment + + +class PersonPageResponseTopicsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Topic name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: PersonPageResponseTopicsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + 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/arcmira/types/person_page_response_topics_item_sentiment.py b/src/arcmira/types/person_page_response_topics_item_sentiment.py new file mode 100644 index 0000000..a2e70c5 --- /dev/null +++ b/src/arcmira/types/person_page_response_topics_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PersonPageResponseTopicsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/product_page_response.py b/src/arcmira/types/product_page_response.py new file mode 100644 index 0000000..15b90a3 --- /dev/null +++ b/src/arcmira/types/product_page_response.py @@ -0,0 +1,83 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_page_mention import EntityPageMention +from .exposure_meta import ExposureMeta +from .product_page_response_channels_item import ProductPageResponseChannelsItem +from .product_page_response_entity import ProductPageResponseEntity +from .product_page_response_mentions_by_month_item import ProductPageResponseMentionsByMonthItem +from .product_page_response_opportunities import ProductPageResponseOpportunities +from .product_page_response_organizations_item import ProductPageResponseOrganizationsItem +from .product_page_response_people_item import ProductPageResponsePeopleItem +from .product_page_response_stats import ProductPageResponseStats +from .product_page_response_topics_item import ProductPageResponseTopicsItem + + +class ProductPageResponse(UniversalBaseModel): + entity: ProductPageResponseEntity = pydantic.Field() + """ + The product and who owns it. + """ + + stats: ProductPageResponseStats = pydantic.Field() + """ + Headline numbers and section totals for the product. + """ + + opportunities: ProductPageResponseOpportunities = pydantic.Field() + """ + Reserved for an advertiser view. Both counts are disabled and always 0; 0 means not computed, not a real count. + """ + + mentions_by_month: typing_extensions.Annotated[ + typing.List[ProductPageResponseMentionsByMonthItem], + FieldMetadata(alias="mentionsByMonth"), + pydantic.Field( + alias="mentionsByMonth", + description="Media mentioning the product per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart.", + ), + ] + """ + Media mentioning the product per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart. + """ + + topics: typing.List[ProductPageResponseTopicsItem] = pydantic.Field() + """ + Up to 10 co-occurring topics, highest count first. + """ + + people: typing.List[ProductPageResponsePeopleItem] = pydantic.Field() + """ + Up to 10 co-occurring people, highest count first. + """ + + organizations: typing.List[ProductPageResponseOrganizationsItem] = pydantic.Field() + """ + Up to 10 co-occurring organizations, highest count first. + """ + + channels: typing.List[ProductPageResponseChannelsItem] = pydantic.Field() + """ + Up to 6 channels that mention the product, highest count first. + """ + + mentions: typing.List[EntityPageMention] = pydantic.Field() + """ + Newest media that mention the entity, one row per video, cut to the plan's media rows. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/product_page_response_channels_item.py b/src/arcmira/types/product_page_response_channels_item.py new file mode 100644 index 0000000..6d98db9 --- /dev/null +++ b/src/arcmira/types/product_page_response_channels_item.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .product_page_response_channels_item_sentiment import ProductPageResponseChannelsItemSentiment + + +class ProductPageResponseChannelsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Channel name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media on this channel that mention the entity. Null when the plan hides counts. + """ + + sentiment: ProductPageResponseChannelsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page key on arcmira.com: its @handle when known, else its UC id. Null when neither is known. + """ + + 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/arcmira/types/product_page_response_channels_item_sentiment.py b/src/arcmira/types/product_page_response_channels_item_sentiment.py new file mode 100644 index 0000000..3793826 --- /dev/null +++ b/src/arcmira/types/product_page_response_channels_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductPageResponseChannelsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/product_page_response_entity.py b/src/arcmira/types/product_page_response_entity.py new file mode 100644 index 0000000..e7a23cd --- /dev/null +++ b/src/arcmira/types/product_page_response_entity.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .product_page_response_entity_owner import ProductPageResponseEntityOwner +from .product_page_response_entity_parent_org import ProductPageResponseEntityParentOrg +from .product_page_response_entity_type import ProductPageResponseEntityType + + +class ProductPageResponseEntity(UniversalBaseModel): + """ + The product and who owns it. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Product name. + """ + + type: ProductPageResponseEntityType = pydantic.Field() + """ + Always product. + """ + + category: str = pydantic.Field() + """ + The stored platform, or Software when none is stored. + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until resolved."), + ] = None + """ + Logo URL. Null until resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + is_priority: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isPriority"), + pydantic.Field(alias="isPriority", description="True when the entity is flagged priority."), + ] + """ + True when the entity is flagged priority. + """ + + owner: typing.Optional[ProductPageResponseEntityOwner] = pydantic.Field(default=None) + """ + The organization or person that owns this entity. Null when no owner is recorded. + """ + + parent_org: typing_extensions.Annotated[ + typing.Optional[ProductPageResponseEntityParentOrg], + FieldMetadata(alias="parentOrg"), + pydantic.Field( + alias="parentOrg", + description="Legacy: the organization recorded as owning the product in entity relations. Null when none. Prefer owner.", + ), + ] = None + """ + Legacy: the organization recorded as owning the product in entity relations. Null when none. Prefer owner. + """ + + 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/arcmira/types/product_page_response_entity_owner.py b/src/arcmira/types/product_page_response_entity_owner.py new file mode 100644 index 0000000..0ec8172 --- /dev/null +++ b/src/arcmira/types/product_page_response_entity_owner.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 + + +class ProductPageResponseEntityOwner(UniversalBaseModel): + """ + The organization or person that owns this entity. Null when no owner is recorded. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id of the owner. + """ + + name: str = pydantic.Field() + """ + Owner name. + """ + + type: str = pydantic.Field() + """ + Owner entity type: organization (legacy rows may read company or brand) or person. + """ + + route: str = pydantic.Field() + """ + Site-relative route of the owner page on arcmira.com. + """ + + 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/arcmira/types/product_page_response_entity_parent_org.py b/src/arcmira/types/product_page_response_entity_parent_org.py new file mode 100644 index 0000000..6bb6769 --- /dev/null +++ b/src/arcmira/types/product_page_response_entity_parent_org.py @@ -0,0 +1,31 @@ +# 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 ProductPageResponseEntityParentOrg(UniversalBaseModel): + """ + Legacy: the organization recorded as owning the product in entity relations. Null when none. Prefer owner. + """ + + name: str = pydantic.Field() + """ + Parent organization name. + """ + + slug: str = pydantic.Field() + """ + The name lowercased with spaces as hyphens. Not guaranteed to match the organization page slug. + """ + + 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/arcmira/types/product_page_response_entity_type.py b/src/arcmira/types/product_page_response_entity_type.py new file mode 100644 index 0000000..2363f37 --- /dev/null +++ b/src/arcmira/types/product_page_response_entity_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductPageResponseEntityType = typing.Union[typing.Literal["product"], typing.Any] diff --git a/src/arcmira/types/product_page_response_mentions_by_month_item.py b/src/arcmira/types/product_page_response_mentions_by_month_item.py new file mode 100644 index 0000000..b37e072 --- /dev/null +++ b/src/arcmira/types/product_page_response_mentions_by_month_item.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ProductPageResponseMentionsByMonthItem(UniversalBaseModel): + month: str = pydantic.Field() + """ + Three-letter month name, e.g. Jan. + """ + + year_month: typing_extensions.Annotated[ + str, FieldMetadata(alias="yearMonth"), pydantic.Field(alias="yearMonth", description="The month as YYYY-MM.") + ] + """ + The month as YYYY-MM. + """ + + count: int = pydantic.Field() + """ + Media that month. Months inside the withheld window read 0. + """ + + 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/arcmira/types/product_page_response_opportunities.py b/src/arcmira/types/product_page_response_opportunities.py new file mode 100644 index 0000000..a2b524a --- /dev/null +++ b/src/arcmira/types/product_page_response_opportunities.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ProductPageResponseOpportunities(UniversalBaseModel): + """ + Reserved for an advertiser view. Both counts are disabled and always 0; 0 means not computed, not a real count. + """ + + evangelists: float = pydantic.Field() + """ + Not computed. Always 0. + """ + + ad_inventory: typing_extensions.Annotated[ + float, + FieldMetadata(alias="adInventory"), + pydantic.Field(alias="adInventory", description="Not computed. Always 0."), + ] + """ + Not computed. Always 0. + """ + + 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/arcmira/types/product_page_response_organizations_item.py b/src/arcmira/types/product_page_response_organizations_item.py new file mode 100644 index 0000000..b6fdbba --- /dev/null +++ b/src/arcmira/types/product_page_response_organizations_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .product_page_response_organizations_item_sentiment import ProductPageResponseOrganizationsItemSentiment + + +class ProductPageResponseOrganizationsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: ProductPageResponseOrganizationsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/product_page_response_organizations_item_sentiment.py b/src/arcmira/types/product_page_response_organizations_item_sentiment.py new file mode 100644 index 0000000..ae8686a --- /dev/null +++ b/src/arcmira/types/product_page_response_organizations_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductPageResponseOrganizationsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/product_page_response_people_item.py b/src/arcmira/types/product_page_response_people_item.py new file mode 100644 index 0000000..882d3bf --- /dev/null +++ b/src/arcmira/types/product_page_response_people_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .product_page_response_people_item_sentiment import ProductPageResponsePeopleItemSentiment + + +class ProductPageResponsePeopleItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: ProductPageResponsePeopleItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/product_page_response_people_item_sentiment.py b/src/arcmira/types/product_page_response_people_item_sentiment.py new file mode 100644 index 0000000..3d84542 --- /dev/null +++ b/src/arcmira/types/product_page_response_people_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductPageResponsePeopleItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/product_page_response_stats.py b/src/arcmira/types/product_page_response_stats.py new file mode 100644 index 0000000..c357e41 --- /dev/null +++ b/src/arcmira/types/product_page_response_stats.py @@ -0,0 +1,84 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class ProductPageResponseStats(UniversalBaseModel): + """ + Headline numbers and section totals for the product. + """ + + velocity: typing.Optional[int] = pydantic.Field(default=None) + """ + Mentions in the last 90 days. Null when the plan hides it. + """ + + sentiment: float = pydantic.Field() + """ + Reserved. Always 0. + """ + + reach: str = pydantic.Field() + """ + Total views as display text, e.g. 1.2M. + """ + + reach_raw: typing_extensions.Annotated[ + float, + FieldMetadata(alias="reachRaw"), + pydantic.Field(alias="reachRaw", description="Total views of the media that mention the product."), + ] + """ + Total views of the media that mention the product. + """ + + total: int = pydantic.Field() + """ + Total media that mention the product. + """ + + latest_media_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="latestMediaAt"), + pydantic.Field( + alias="latestMediaAt", + description="Newest publish date among the counted media. Null when none. The freshness gate does not withhold it.", + ), + ] = None + """ + Newest publish date among the counted media. Null when none. The freshness gate does not withhold it. + """ + + people: int = pydantic.Field() + """ + Total co-occurring people. + """ + + topics: int = pydantic.Field() + """ + Total co-occurring topics. + """ + + organizations: int = pydantic.Field() + """ + Total co-occurring organizations. + """ + + channels: int = pydantic.Field() + """ + Total channels that mention the product. + """ + + 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/arcmira/types/product_page_response_topics_item.py b/src/arcmira/types/product_page_response_topics_item.py new file mode 100644 index 0000000..abea1b8 --- /dev/null +++ b/src/arcmira/types/product_page_response_topics_item.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 .product_page_response_topics_item_sentiment import ProductPageResponseTopicsItemSentiment + + +class ProductPageResponseTopicsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Topic name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: ProductPageResponseTopicsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + 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/arcmira/types/product_page_response_topics_item_sentiment.py b/src/arcmira/types/product_page_response_topics_item_sentiment.py new file mode 100644 index 0000000..7cdbeff --- /dev/null +++ b/src/arcmira/types/product_page_response_topics_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProductPageResponseTopicsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/published_excerpt.py b/src/arcmira/types/published_excerpt.py new file mode 100644 index 0000000..0b41ded --- /dev/null +++ b/src/arcmira/types/published_excerpt.py @@ -0,0 +1,106 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .published_excerpt_public_source_class import PublishedExcerptPublicSourceClass + + +class PublishedExcerpt(UniversalBaseModel): + """ + A speakerless excerpt naming the entity in this media, when one is active. Mention rows only. + """ + + id: str = pydantic.Field() + """ + Published excerpt id. + """ + + exact_text: typing_extensions.Annotated[ + str, + FieldMetadata(alias="exactText"), + pydantic.Field(alias="exactText", description="The transcript span that names the entity."), + ] + """ + The transcript span that names the entity. + """ + + context_before: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="contextBefore"), + pydantic.Field(alias="contextBefore", description="Transcript text immediately before the span."), + ] = None + """ + Transcript text immediately before the span. + """ + + context_after: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="contextAfter"), + pydantic.Field(alias="contextAfter", description="Transcript text immediately after the span."), + ] = None + """ + Transcript text immediately after the span. + """ + + term: str = pydantic.Field() + """ + The surface form that matched in the transcript, which may be an approved alias. + """ + + term_char_start: typing_extensions.Annotated[ + int, + FieldMetadata(alias="termCharStart"), + pydantic.Field(alias="termCharStart", description="Character offset where term starts."), + ] + """ + Character offset where term starts. + """ + + term_char_end: typing_extensions.Annotated[ + int, + FieldMetadata(alias="termCharEnd"), + pydantic.Field(alias="termCharEnd", description="Character offset where term ends."), + ] + """ + Character offset where term ends. + """ + + start_seconds: typing_extensions.Annotated[ + float, + FieldMetadata(alias="startSeconds"), + pydantic.Field(alias="startSeconds", description="Span start in the video, in seconds."), + ] + """ + Span start in the video, in seconds. + """ + + end_seconds: typing_extensions.Annotated[ + typing.Optional[float], + FieldMetadata(alias="endSeconds"), + pydantic.Field(alias="endSeconds", description="Span end in the video, in seconds."), + ] = None + """ + Span end in the video, in seconds. + """ + + public_source_class: typing_extensions.Annotated[ + PublishedExcerptPublicSourceClass, + FieldMetadata(alias="publicSourceClass"), + pydantic.Field(alias="publicSourceClass", description="Transcript source class of the excerpt."), + ] + """ + Transcript source class of the 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/arcmira/types/published_excerpt_public_source_class.py b/src/arcmira/types/published_excerpt_public_source_class.py new file mode 100644 index 0000000..8f44f67 --- /dev/null +++ b/src/arcmira/types/published_excerpt_public_source_class.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PublishedExcerptPublicSourceClass = typing.Union[ + typing.Literal["arcmira_premium_excerpt", "creator_captions", "third_party_quick"], typing.Any +] diff --git a/src/arcmira/types/recommendation.py b/src/arcmira/types/recommendation.py new file mode 100644 index 0000000..e89ba69 --- /dev/null +++ b/src/arcmira/types/recommendation.py @@ -0,0 +1,101 @@ +# 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 .entity_ref import EntityRef +from .recommendation_media import RecommendationMedia + + +class Recommendation(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public recommendation id in the form "com_{n}". + """ + + recommendation_id: int = pydantic.Field() + """ + Raw integer id of the recommendation row. Same number as in the "com_{n}" public id. + """ + + mention_class: str = pydantic.Field() + """ + Commercial mention classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention). + """ + + entity: EntityRef + media: RecommendationMedia + start_timestamp: str = pydantic.Field() + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds. + """ + + end_timestamp: str = pydantic.Field() + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds. + """ + + start_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Start position in the video in integer SECONDS, parsed from start_timestamp. Prefer this over the deprecated string field. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + end_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + End position in the video in integer SECONDS, parsed from end_timestamp. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + verbatim_quote: typing.Optional[str] = pydantic.Field(default=None) + """ + Verbatim quote from the transcript. Null when no quote was extracted. + """ + + promo_code: typing.Optional[str] = pydantic.Field(default=None) + """ + Promo code read out in the mention. Null unless one was detected. + """ + + offer: typing.Optional[str] = pydantic.Field(default=None) + """ + Offer text, e.g. "20% off your first order". Null unless one was detected. + """ + + sentiment: typing.Optional[float] = pydantic.Field(default=None) + """ + DEPRECATED: use sentiment_score, which carries the same number. Removal will be announced in the changelog. Raw NUMERIC sentiment score between -1 and 1. Null when not computed. + """ + + sentiment_score: typing.Optional[float] = pydantic.Field(default=None) + """ + Raw sentiment score between -1 and 1, same semantics as sentiment_score on mention rows. Null when not computed. + """ + + confidence: float = pydantic.Field() + """ + Classifier confidence between 0 and 1. Rows below the min_confidence filter (default 0.7) are excluded from list responses. + """ + + speaker_role: str = pydantic.Field() + """ + Role of the speaker delivering the mention, e.g. "host" or "guest". + """ + + conflict_status: typing.Optional[str] = pydantic.Field(default=None) + """ + Set when community feedback disputes the classification (e.g. "disputed"). Null when undisputed. Disputed rows are excluded unless include_disputed=true. + """ + + resolution: typing.Optional[str] = pydantic.Field(default=None) + """ + How a disputed classification was resolved. Null until a dispute has been resolved. + """ + + 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/arcmira/types/recommendation_enrichment_item.py b/src/arcmira/types/recommendation_enrichment_item.py new file mode 100644 index 0000000..1226a26 --- /dev/null +++ b/src/arcmira/types/recommendation_enrichment_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 RecommendationEnrichmentItem(UniversalBaseModel): + id: str = pydantic.Field() + """ + Public recommendation id in the form "com_{n}". + """ + + mention_class: str = pydantic.Field() + """ + Commercial mention classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention). + """ + + verbatim_quote: typing.Optional[str] = pydantic.Field(default=None) + """ + Verbatim quote from the transcript. Null when no quote was extracted. + """ + + promo_code: typing.Optional[str] = pydantic.Field(default=None) + """ + Promo code read out in the mention. Null unless one was detected. + """ + + offer: typing.Optional[str] = pydantic.Field(default=None) + """ + Offer text, e.g. "20% off". Null unless one was detected. + """ + + confidence: float = pydantic.Field() + """ + Classifier confidence between 0 and 1. + """ + + start_timestamp: str = pydantic.Field() + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use start_seconds. + """ + + end_timestamp: str = pydantic.Field() + """ + DEPRECATED: prefer the numeric sibling field. This "MM:SS" (or "HH:MM:SS") string remains until a dated, changelog-announced removal (see /requests#versioning-and-deprecation). Use end_seconds. + """ + + start_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Start position in the video in integer SECONDS, parsed from start_timestamp. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + end_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + End position in the video in integer SECONDS, parsed from end_timestamp. 0 means "full episode / no specific moment" (the string sentinel "00:00"). Null when the string timestamp is null or unparseable. + """ + + 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/arcmira/types/recommendation_list_response.py b/src/arcmira/types/recommendation_list_response.py new file mode 100644 index 0000000..4d68a67 --- /dev/null +++ b/src/arcmira/types/recommendation_list_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 .recommendation import Recommendation +from .recommendation_list_response_entity import RecommendationListResponseEntity + + +class RecommendationListResponse(UniversalBaseModel): + data: typing.List[Recommendation] + has_more: bool = pydantic.Field() + """ + True when more rows exist past this page. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last page. + """ + + entity: RecommendationListResponseEntity = pydantic.Field() + """ + The resolved entity the recommendations belong to. + """ + + 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/arcmira/types/recommendation_list_response_entity.py b/src/arcmira/types/recommendation_list_response_entity.py new file mode 100644 index 0000000..7c8eeb3 --- /dev/null +++ b/src/arcmira/types/recommendation_list_response_entity.py @@ -0,0 +1,111 @@ +# 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 RecommendationListResponseEntity(UniversalBaseModel): + """ + The resolved entity the recommendations belong to. + """ + + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". Always the canonical entity id. + """ + + numeric_id: int = pydantic.Field() + """ + Raw integer database id of the canonical entity. Prefer the public "ent_{n}" id in requests. + """ + + canonical_id: str = pydantic.Field() + """ + Public id of the canonical entity. Identical to id. + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + platform: typing.Optional[str] = pydantic.Field(default=None) + """ + Source platform for channel entities, e.g. "youtube". Null unless the entity is platform-bound. + """ + + url: typing.Optional[str] = pydantic.Field(default=None) + """ + Canonical external URL for the entity. Null when none is known. + """ + + image_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity image URL. Null until an image has been resolved. + """ + + image_checked_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Timestamp of the last image resolution attempt. Null until the image pipeline has visited this entity. + """ + + appearance_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of indexed appearance/mention rows for this entity. 0 when never counted. + """ + + owner_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("ent_{n}") of the owning entity, e.g. the organization behind a product. Null unless an ownership link exists. + """ + + is_canonical: bool = pydantic.Field() + """ + True when the id you supplied is the canonical entity. False when your id was merged into this canonical record. + """ + + merged_from_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id you supplied when it differs from the canonical entity, i.e. your id was merged into this record. Null unless a merge redirect happened. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug, the site's canonical id for every type but channel. Null when never slugged. + """ + + route: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative route of this entity's page on arcmira.com, e.g. "/org/ramp", "/person/jane-doe", "/yt/@TBPNLive". Null for a type the site has no page for. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + appearances_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of their appearances on arcmira.com (their page opens on it). Null for every other type. + """ + + mentions_page: typing.Optional[str] = pydantic.Field(default=None) + """ + For a person, the list of mentions of them on arcmira.com, page/mentions. Null for every other type: their page is the mentions list already. + """ + + 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/arcmira/types/recommendation_media.py b/src/arcmira/types/recommendation_media.py new file mode 100644 index 0000000..bbf6f57 --- /dev/null +++ b/src/arcmira/types/recommendation_media.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 .recommendation_media_source_channel import RecommendationMediaSourceChannel + + +class RecommendationMedia(UniversalBaseModel): + video_id: str = pydantic.Field() + """ + YouTube video id (11 characters). + """ + + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title. + """ + + published_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Video publish timestamp. + """ + + channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id of the source channel. + """ + + source_channel: typing.Optional[RecommendationMediaSourceChannel] = pydantic.Field(default=None) + """ + The channel entity that published the video. Null when the video has not been linked to a channel entity. + """ + + 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/arcmira/types/recommendation_media_source_channel.py b/src/arcmira/types/recommendation_media_source_channel.py new file mode 100644 index 0000000..66828c3 --- /dev/null +++ b/src/arcmira/types/recommendation_media_source_channel.py @@ -0,0 +1,31 @@ +# 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 RecommendationMediaSourceChannel(UniversalBaseModel): + """ + The channel entity that published the video. Null when the video has not been linked to a channel entity. + """ + + id: str = pydantic.Field() + """ + Public entity id ("ent_{n}") of the source channel. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Source channel 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/arcmira/types/resolve_candidate.py b/src/arcmira/types/resolve_candidate.py new file mode 100644 index 0000000..0aba140 --- /dev/null +++ b/src/arcmira/types/resolve_candidate.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 +from .resolve_candidate_match import ResolveCandidateMatch + + +class ResolveCandidate(UniversalBaseModel): + """ + The one row q means. Set on exact and single_fuzzy only. Name it in the answer. + """ + + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug. Null when the entity has never been slugged. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + appearance_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of indexed appearance/mention rows. Results are ordered by this, descending. + """ + + youtube_channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id for channel entities. Null for every other type. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + One catalog sentence that tells rows with the same name apart, for example "Common gender-neutral given name or nickname". Null when the catalog has none. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + match: ResolveCandidateMatch = pydantic.Field() + """ + How the row's name relates to q: the whole name, a run of its words (Michael Jordan for Jordan), characters inside a word, the show's initials (My First Million for MFM), or a near spelling. + """ + + 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/arcmira/types/resolve_candidate_match.py b/src/arcmira/types/resolve_candidate_match.py new file mode 100644 index 0000000..9aef1cc --- /dev/null +++ b/src/arcmira/types/resolve_candidate_match.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ResolveCandidateMatch = typing.Union[typing.Literal["exact", "word", "substring", "acronym", "spelling"], typing.Any] diff --git a/src/arcmira/types/resolve_suggestion.py b/src/arcmira/types/resolve_suggestion.py new file mode 100644 index 0000000..e185c08 --- /dev/null +++ b/src/arcmira/types/resolve_suggestion.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 +from .resolve_suggestion_match import ResolveSuggestionMatch +from .resolve_suggestion_reason import ResolveSuggestionReason + + +class ResolveSuggestion(UniversalBaseModel): + """ + Set when best is null but one row stands out, with the reason and evidence. Use it and tell the user you assumed it. + """ + + id: str = pydantic.Field() + """ + Public entity id in the form "ent_{n}". + """ + + name: str = pydantic.Field() + """ + Entity name. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + URL slug. Null when the entity has never been slugged. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + appearance_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of indexed appearance/mention rows. Results are ordered by this, descending. + """ + + youtube_channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id for channel entities. Null for every other type. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + One catalog sentence that tells rows with the same name apart, for example "Common gender-neutral given name or nickname". Null when the catalog has none. + """ + + page: typing.Optional[str] = pydantic.Field(default=None) + """ + The entity's page on arcmira.com, absolute. Link the name to it when you write the entity into an answer. Null for a type the site has no page for. + """ + + match: ResolveSuggestionMatch = pydantic.Field() + """ + How the row's name relates to q: the whole name, a run of its words (Michael Jordan for Jordan), characters inside a word, the show's initials (My First Million for MFM), or a near spelling. + """ + + reason: ResolveSuggestionReason = pydantic.Field() + """ + Why this row stands out: dominant (10x the appearances of the next match), only_word_match, context (the context parameter points at it), acronym, spelling. + """ + + evidence: str = pydantic.Field() + """ + The numbers or words behind the reason, to repeat to the user. + """ + + assumed: bool = pydantic.Field() + """ + Always true: this is an assumption the answer must state. + """ + + 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/arcmira/types/resolve_suggestion_match.py b/src/arcmira/types/resolve_suggestion_match.py new file mode 100644 index 0000000..1e7f743 --- /dev/null +++ b/src/arcmira/types/resolve_suggestion_match.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ResolveSuggestionMatch = typing.Union[typing.Literal["exact", "word", "substring", "acronym", "spelling"], typing.Any] diff --git a/src/arcmira/types/resolve_suggestion_reason.py b/src/arcmira/types/resolve_suggestion_reason.py new file mode 100644 index 0000000..b78f974 --- /dev/null +++ b/src/arcmira/types/resolve_suggestion_reason.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ResolveSuggestionReason = typing.Union[ + typing.Literal["dominant", "only_word_match", "context", "acronym", "spelling"], typing.Any +] diff --git a/src/arcmira/types/search_request_type.py b/src/arcmira/types/search_request_type.py new file mode 100644 index 0000000..c31bdbb --- /dev/null +++ b/src/arcmira/types/search_request_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SearchRequestType = typing.Union[typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any] diff --git a/src/arcmira/types/search_resolve_response.py b/src/arcmira/types/search_resolve_response.py new file mode 100644 index 0000000..1166cbe --- /dev/null +++ b/src/arcmira/types/search_resolve_response.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .search_resolve_response_entity import SearchResolveResponseEntity + + +class SearchResolveResponse(UniversalBaseModel): + found: bool = pydantic.Field() + """ + True when the query resolved to exactly one entity. + """ + + entity: typing.Optional[SearchResolveResponseEntity] = pydantic.Field(default=None) + """ + The resolved entity. Absent when found is false. + """ + + route: typing.Optional[str] = pydantic.Field(default=None) + """ + Site-relative route for the entity on arcmira.com. Absent when found is false. + """ + + from_cache: typing_extensions.Annotated[ + typing.Optional[bool], + FieldMetadata(alias="fromCache"), + pydantic.Field( + alias="fromCache", description="True when the result was served from the 5-minute resolver cache." + ), + ] = None + """ + True when the result was served from the 5-minute resolver cache. + """ + + 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/arcmira/types/search_resolve_response_entity.py b/src/arcmira/types/search_resolve_response_entity.py new file mode 100644 index 0000000..353d043 --- /dev/null +++ b/src/arcmira/types/search_resolve_response_entity.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 SearchResolveResponseEntity(UniversalBaseModel): + """ + The resolved entity. Absent when found is false. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id. Use "ent_{id}" with the /v1/entities endpoints. + """ + + name: str = pydantic.Field() + """ + Canonical entity name. + """ + + type: str = pydantic.Field() + """ + Entity type. Values: person (an individual), organization (a company or institution; legacy rows may read company or brand), product (a product or service), topic (a subject or theme), channel (a media source such as a YouTube channel), unknown (type was never classified). + """ + + 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/arcmira/types/signup_sent_response.py b/src/arcmira/types/signup_sent_response.py new file mode 100644 index 0000000..25c0466 --- /dev/null +++ b/src/arcmira/types/signup_sent_response.py @@ -0,0 +1,28 @@ +# 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 .signup_sent_response_next import SignupSentResponseNext + + +class SignupSentResponse(UniversalBaseModel): + next: SignupSentResponseNext = pydantic.Field() + """ + The call that completes the signup. + """ + + expires_in: int = pydantic.Field() + """ + Seconds the code stays valid, from the moment it was sent. + """ + + 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/arcmira/types/signup_sent_response_next.py b/src/arcmira/types/signup_sent_response_next.py new file mode 100644 index 0000000..2188a1b --- /dev/null +++ b/src/arcmira/types/signup_sent_response_next.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 +from .signup_sent_response_next_method import SignupSentResponseNextMethod + + +class SignupSentResponseNext(UniversalBaseModel): + """ + The call that completes the signup. + """ + + method: SignupSentResponseNextMethod = pydantic.Field() + """ + Always POST. + """ + + url: str = pydantic.Field() + """ + The verify call, absolute: POST it with { email, code } once the code arrives. + """ + + 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/arcmira/types/signup_sent_response_next_method.py b/src/arcmira/types/signup_sent_response_next_method.py new file mode 100644 index 0000000..346a6ae --- /dev/null +++ b/src/arcmira/types/signup_sent_response_next_method.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SignupSentResponseNextMethod = typing.Union[typing.Literal["POST"], typing.Any] diff --git a/src/arcmira/types/signup_verified_response.py b/src/arcmira/types/signup_verified_response.py new file mode 100644 index 0000000..7601771 --- /dev/null +++ b/src/arcmira/types/signup_verified_response.py @@ -0,0 +1,57 @@ +# 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 SignupVerifiedResponse(UniversalBaseModel): + key: str = pydantic.Field() + """ + The arc_sk_ account key. Shown once. Send it as Authorization: Bearer. + """ + + key_id: str = pydantic.Field() + """ + The key id. Appears on funnel events and in the dashboard; never a secret. + """ + + header: str = pydantic.Field() + """ + The pasteable request header line: Authorization: Bearer arc_sk_... + """ + + scopes: typing.List[str] = pydantic.Field() + """ + Always ["read"]. Create an Admin key in the dashboard for account write access. + """ + + tier: str = pydantic.Field() + """ + The plan the account is on: free for a new account, its plan when the address already had one. + """ + + rows_allotted: int = pydantic.Field() + """ + The pool this key draws on: a free account's lifetime credits, a paid plan's monthly rows. + """ + + next: str = pydantic.Field() + """ + A curl command for the first call: GET /v1/me with the key. + """ + + docs_url: str = pydantic.Field() + """ + Sign-up documentation. + """ + + 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/arcmira/types/speaker_identification_submitted_response.py b/src/arcmira/types/speaker_identification_submitted_response.py new file mode 100644 index 0000000..d66ef85 --- /dev/null +++ b/src/arcmira/types/speaker_identification_submitted_response.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 +from .speaker_identification_submitted_response_identification import ( + SpeakerIdentificationSubmittedResponseIdentification, +) + + +class SpeakerIdentificationSubmittedResponse(UniversalBaseModel): + identification: SpeakerIdentificationSubmittedResponseIdentification = pydantic.Field() + """ + The stored pending identification. + """ + + 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/arcmira/types/speaker_identification_submitted_response_identification.py b/src/arcmira/types/speaker_identification_submitted_response_identification.py new file mode 100644 index 0000000..33f68e1 --- /dev/null +++ b/src/arcmira/types/speaker_identification_submitted_response_identification.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .speaker_identification_submitted_response_identification_entity import ( + SpeakerIdentificationSubmittedResponseIdentificationEntity, +) +from .speaker_identification_submitted_response_identification_status import ( + SpeakerIdentificationSubmittedResponseIdentificationStatus, +) + + +class SpeakerIdentificationSubmittedResponseIdentification(UniversalBaseModel): + """ + The stored pending identification. + """ + + id: int = pydantic.Field() + """ + Pending row id. Withdraw it with DELETE /v1/transcripts/{video_id}/speakers/{id}. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + speaker_id: typing_extensions.Annotated[ + int, + FieldMetadata(alias="speakerId"), + pydantic.Field( + alias="speakerId", description="The speaker id you sent, from the transcript read's speakers[]." + ), + ] + """ + The speaker id you sent, from the transcript read's speakers[]. + """ + + entity: SpeakerIdentificationSubmittedResponseIdentificationEntity = pydantic.Field() + """ + The person the speaker now points at: the entity you named, an existing person matching the name, or a newly created one. + """ + + status: SpeakerIdentificationSubmittedResponseIdentificationStatus = pydantic.Field() + """ + Review status. Always pending on submit. + """ + + 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/arcmira/types/speaker_identification_submitted_response_identification_entity.py b/src/arcmira/types/speaker_identification_submitted_response_identification_entity.py new file mode 100644 index 0000000..72f80be --- /dev/null +++ b/src/arcmira/types/speaker_identification_submitted_response_identification_entity.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 SpeakerIdentificationSubmittedResponseIdentificationEntity(UniversalBaseModel): + """ + The person the speaker now points at: the entity you named, an existing person matching the name, or a newly created one. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id of the person. + """ + + name: str = pydantic.Field() + """ + Person name. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + Person slug. Null when never slugged. + """ + + 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/arcmira/types/speaker_identification_submitted_response_identification_status.py b/src/arcmira/types/speaker_identification_submitted_response_identification_status.py new file mode 100644 index 0000000..5d884e3 --- /dev/null +++ b/src/arcmira/types/speaker_identification_submitted_response_identification_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SpeakerIdentificationSubmittedResponseIdentificationStatus = typing.Union[ + typing.Literal["pending", "approved", "rejected", "withdrawn", "reverted"], typing.Any +] diff --git a/src/arcmira/types/stale_metadata_change.py b/src/arcmira/types/stale_metadata_change.py new file mode 100644 index 0000000..f14a195 --- /dev/null +++ b/src/arcmira/types/stale_metadata_change.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 StaleMetadataChange(UniversalBaseModel): + """ + For issue_type stale_metadata: the field and its correct value, with a source URL when you have one. + """ + + field: typing.Optional[str] = pydantic.Field(default=None) + """ + The metadata field that is outdated, e.g. "website" or "name". + """ + + value: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + The current, correct value. + """ + + source_url: typing.Optional[str] = pydantic.Field(default=None) + """ + URL evidencing the correct value. + """ + + 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/arcmira/types/team_member.py b/src/arcmira/types/team_member.py new file mode 100644 index 0000000..63ce503 --- /dev/null +++ b/src/arcmira/types/team_member.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 .team_member_role import TeamMemberRole +from .team_member_seat_type import TeamMemberSeatType + + +class TeamMember(UniversalBaseModel): + user_id: str = pydantic.Field() + """ + Id of the member's user account. + """ + + name: str = pydantic.Field() + """ + Member display name. + """ + + email: str = pydantic.Field() + """ + Member email address. + """ + + role: TeamMemberRole = pydantic.Field() + """ + Team role. Values: owner, admin, member, unpaid_admin. Unpaid admins manage the team without a paid seat and have no product access. + """ + + seat_type: TeamMemberSeatType = pydantic.Field() + """ + Seat type. Values: standard (5,000 rows/month included), premium (25,000 rows/month included), free (the Unpaid Admin seat: no product access). + """ + + joined_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the member joined the team. Null when unknown. + """ + + 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/arcmira/types/team_member_role.py b/src/arcmira/types/team_member_role.py new file mode 100644 index 0000000..84119c8 --- /dev/null +++ b/src/arcmira/types/team_member_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TeamMemberRole = typing.Union[typing.Literal["owner", "admin", "member", "unpaid_admin"], typing.Any] diff --git a/src/arcmira/types/team_member_seat_type.py b/src/arcmira/types/team_member_seat_type.py new file mode 100644 index 0000000..fb6790e --- /dev/null +++ b/src/arcmira/types/team_member_seat_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TeamMemberSeatType = typing.Union[typing.Literal["standard", "premium", "free"], typing.Any] diff --git a/src/arcmira/types/team_member_spend.py b/src/arcmira/types/team_member_spend.py new file mode 100644 index 0000000..898678f --- /dev/null +++ b/src/arcmira/types/team_member_spend.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 .team_member_spend_role import TeamMemberSpendRole +from .team_member_spend_seat_type import TeamMemberSpendSeatType + + +class TeamMemberSpend(UniversalBaseModel): + user_id: str = pydantic.Field() + """ + Id of the member's user account. + """ + + name: str = pydantic.Field() + """ + Member display name. + """ + + email: str = pydantic.Field() + """ + Member email address. + """ + + role: TeamMemberSpendRole = pydantic.Field() + """ + Team role. Values: owner, admin, member, unpaid_admin. Unpaid admins manage the team without a paid seat and have no product access. + """ + + seat_type: TeamMemberSpendSeatType = pydantic.Field() + """ + Seat type. Values: standard (5,000 rows/month included), premium (25,000 rows/month included), free (the Unpaid Admin seat: no product access). + """ + + rows_used: int = pydantic.Field() + """ + Rows this member consumed in the current period. + """ + + on_demand_spend_cents: int = pydantic.Field() + """ + Account-wide on-demand overage spend for this member in the current period, in US cents. + """ + + on_demand_enabled: bool = pydantic.Field() + """ + The member account's on-demand preference. Effective admission also depends on the team setting and remaining spend 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/arcmira/types/team_member_spend_role.py b/src/arcmira/types/team_member_spend_role.py new file mode 100644 index 0000000..34a4de7 --- /dev/null +++ b/src/arcmira/types/team_member_spend_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TeamMemberSpendRole = typing.Union[typing.Literal["owner", "admin", "member", "unpaid_admin"], typing.Any] diff --git a/src/arcmira/types/team_member_spend_seat_type.py b/src/arcmira/types/team_member_spend_seat_type.py new file mode 100644 index 0000000..f49d644 --- /dev/null +++ b/src/arcmira/types/team_member_spend_seat_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TeamMemberSpendSeatType = typing.Union[typing.Literal["standard", "premium", "free"], typing.Any] diff --git a/src/arcmira/types/team_members_response.py b/src/arcmira/types/team_members_response.py new file mode 100644 index 0000000..943c0fa --- /dev/null +++ b/src/arcmira/types/team_members_response.py @@ -0,0 +1,26 @@ +# 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 .team_member import TeamMember +from .team_members_response_team import TeamMembersResponseTeam + + +class TeamMembersResponse(UniversalBaseModel): + data: typing.List[TeamMember] = pydantic.Field() + """ + Active members, earliest join first. Removed members are excluded. + """ + + team: TeamMembersResponseTeam + + 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/arcmira/types/team_members_response_team.py b/src/arcmira/types/team_members_response_team.py new file mode 100644 index 0000000..f467793 --- /dev/null +++ b/src/arcmira/types/team_members_response_team.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 TeamMembersResponseTeam(UniversalBaseModel): + id: str = pydantic.Field() + """ + Team id. + """ + + name: str = pydantic.Field() + """ + Team 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/arcmira/types/team_spend_response.py b/src/arcmira/types/team_spend_response.py new file mode 100644 index 0000000..46473fa --- /dev/null +++ b/src/arcmira/types/team_spend_response.py @@ -0,0 +1,28 @@ +# 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 .team_member_spend import TeamMemberSpend + + +class TeamSpendResponse(UniversalBaseModel): + period_start: str = pydantic.Field() + """ + First day of the current period (YYYY-MM-DD). + """ + + data: typing.List[TeamMemberSpend] = pydantic.Field() + """ + Per-member spend rows, earliest join first. + """ + + 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/arcmira/types/team_usage_event.py b/src/arcmira/types/team_usage_event.py new file mode 100644 index 0000000..b9a6830 --- /dev/null +++ b/src/arcmira/types/team_usage_event.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 TeamUsageEvent(UniversalBaseModel): + id: int = pydantic.Field() + """ + Usage event id. + """ + + user_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Id of the member who generated the event. + """ + + action: str = pydantic.Field() + """ + Usage action, e.g. entity_view, search, export. + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity type the event touched, when applicable. + """ + + entity_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Entity name the event touched, when applicable. + """ + + total_rows: int = pydantic.Field() + """ + Total rows returned by the request. + """ + + premium_rows: int = pydantic.Field() + """ + Rows charged against the member's allowance (total minus free rows). + """ + + request_path: typing.Optional[str] = pydantic.Field(default=None) + """ + API path that generated the event. + """ + + is_overage: bool = pydantic.Field() + """ + True when this event billed on-demand rows past the included allowance. + """ + + created_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the event was recorded. + """ + + 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/arcmira/types/team_usage_events_response.py b/src/arcmira/types/team_usage_events_response.py new file mode 100644 index 0000000..06f760a --- /dev/null +++ b/src/arcmira/types/team_usage_events_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 .team_usage_event import TeamUsageEvent + + +class TeamUsageEventsResponse(UniversalBaseModel): + data: typing.List[TeamUsageEvent] = pydantic.Field() + """ + Usage events across all team members, newest first. + """ + + has_more: bool = pydantic.Field() + """ + True when another page exists. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Opaque cursor for the next page. Null on the last 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/arcmira/types/topic_page_response.py b/src/arcmira/types/topic_page_response.py new file mode 100644 index 0000000..ae1caed --- /dev/null +++ b/src/arcmira/types/topic_page_response.py @@ -0,0 +1,87 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .entity_page_mention import EntityPageMention +from .exposure_meta import ExposureMeta +from .topic_page_response_channels_item import TopicPageResponseChannelsItem +from .topic_page_response_companies_item import TopicPageResponseCompaniesItem +from .topic_page_response_entity import TopicPageResponseEntity +from .topic_page_response_mentions_by_month_item import TopicPageResponseMentionsByMonthItem +from .topic_page_response_products_item import TopicPageResponseProductsItem +from .topic_page_response_related_topics_item import TopicPageResponseRelatedTopicsItem +from .topic_page_response_stats import TopicPageResponseStats +from .topic_page_response_voices_item import TopicPageResponseVoicesItem + + +class TopicPageResponse(UniversalBaseModel): + entity: TopicPageResponseEntity = pydantic.Field() + """ + The topic. + """ + + stats: TopicPageResponseStats = pydantic.Field() + """ + Headline numbers and section totals for the topic. + """ + + mentions_by_month: typing_extensions.Annotated[ + typing.List[TopicPageResponseMentionsByMonthItem], + FieldMetadata(alias="mentionsByMonth"), + pydantic.Field( + alias="mentionsByMonth", + description="Media mentioning the topic per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart.", + ), + ] + """ + Media mentioning the topic per month. Twelve months ending this month, oldest first. Empty when the plan withholds the chart. + """ + + related_topics: typing_extensions.Annotated[ + typing.List[TopicPageResponseRelatedTopicsItem], + FieldMetadata(alias="relatedTopics"), + pydantic.Field(alias="relatedTopics", description="Co-occurring topics, highest count first."), + ] + """ + Co-occurring topics, highest count first. + """ + + voices: typing.List[TopicPageResponseVoicesItem] = pydantic.Field() + """ + People who appeared in media with the topic, highest count first. + """ + + companies: typing.List[TopicPageResponseCompaniesItem] = pydantic.Field() + """ + Co-occurring organizations, highest count first. + """ + + products: typing.List[TopicPageResponseProductsItem] = pydantic.Field() + """ + Co-occurring products, highest count first. + """ + + channels: typing.List[TopicPageResponseChannelsItem] = pydantic.Field() + """ + Channels that mention the topic, highest count first. + """ + + mentions: typing.List[EntityPageMention] = pydantic.Field() + """ + Newest media that mention the entity, one row per video, cut to the plan's media rows. + """ + + meta: typing_extensions.Annotated[ExposureMeta, FieldMetadata(alias="_meta"), pydantic.Field(alias="_meta")] + + 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/arcmira/types/topic_page_response_channels_item.py b/src/arcmira/types/topic_page_response_channels_item.py new file mode 100644 index 0000000..662a7af --- /dev/null +++ b/src/arcmira/types/topic_page_response_channels_item.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .topic_page_response_channels_item_sentiment import TopicPageResponseChannelsItemSentiment + + +class TopicPageResponseChannelsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Channel name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media on this channel that mention the entity. Null when the plan hides counts. + """ + + sentiment: TopicPageResponseChannelsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + slug: typing.Optional[str] = pydantic.Field(default=None) + """ + The channel's page key on arcmira.com: its @handle when known, else its UC id. Null when neither is known. + """ + + 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/arcmira/types/topic_page_response_channels_item_sentiment.py b/src/arcmira/types/topic_page_response_channels_item_sentiment.py new file mode 100644 index 0000000..16d6408 --- /dev/null +++ b/src/arcmira/types/topic_page_response_channels_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseChannelsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/topic_page_response_companies_item.py b/src/arcmira/types/topic_page_response_companies_item.py new file mode 100644 index 0000000..077accf --- /dev/null +++ b/src/arcmira/types/topic_page_response_companies_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .topic_page_response_companies_item_sentiment import TopicPageResponseCompaniesItemSentiment + + +class TopicPageResponseCompaniesItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: TopicPageResponseCompaniesItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/topic_page_response_companies_item_sentiment.py b/src/arcmira/types/topic_page_response_companies_item_sentiment.py new file mode 100644 index 0000000..6000440 --- /dev/null +++ b/src/arcmira/types/topic_page_response_companies_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseCompaniesItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/topic_page_response_entity.py b/src/arcmira/types/topic_page_response_entity.py new file mode 100644 index 0000000..da5713c --- /dev/null +++ b/src/arcmira/types/topic_page_response_entity.py @@ -0,0 +1,68 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .topic_page_response_entity_type import TopicPageResponseEntityType + + +class TopicPageResponseEntity(UniversalBaseModel): + """ + The topic. + """ + + id: int = pydantic.Field() + """ + Raw integer entity id. + """ + + name: str = pydantic.Field() + """ + Topic name. + """ + + type: TopicPageResponseEntityType = pydantic.Field() + """ + Always topic. + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Topic image URL. Null until resolved."), + ] = None + """ + Topic image URL. Null until resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + is_priority: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isPriority"), + pydantic.Field(alias="isPriority", description="True when the entity is flagged priority."), + ] + """ + True when the entity is flagged priority. + """ + + 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/arcmira/types/topic_page_response_entity_type.py b/src/arcmira/types/topic_page_response_entity_type.py new file mode 100644 index 0000000..074816d --- /dev/null +++ b/src/arcmira/types/topic_page_response_entity_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseEntityType = typing.Union[typing.Literal["topic"], typing.Any] diff --git a/src/arcmira/types/topic_page_response_mentions_by_month_item.py b/src/arcmira/types/topic_page_response_mentions_by_month_item.py new file mode 100644 index 0000000..5ab7d05 --- /dev/null +++ b/src/arcmira/types/topic_page_response_mentions_by_month_item.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class TopicPageResponseMentionsByMonthItem(UniversalBaseModel): + month: str = pydantic.Field() + """ + Three-letter month name, e.g. Jan. + """ + + year_month: typing_extensions.Annotated[ + str, FieldMetadata(alias="yearMonth"), pydantic.Field(alias="yearMonth", description="The month as YYYY-MM.") + ] + """ + The month as YYYY-MM. + """ + + count: int = pydantic.Field() + """ + Media that month. Months inside the withheld window read 0. + """ + + 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/arcmira/types/topic_page_response_products_item.py b/src/arcmira/types/topic_page_response_products_item.py new file mode 100644 index 0000000..555c396 --- /dev/null +++ b/src/arcmira/types/topic_page_response_products_item.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .topic_page_response_products_item_sentiment import TopicPageResponseProductsItemSentiment + + +class TopicPageResponseProductsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Entity name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: TopicPageResponseProductsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + logo_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoUrl"), + pydantic.Field(alias="logoUrl", description="Logo URL. Null until an image has been resolved."), + ] = None + """ + Logo URL. Null until an image has been resolved. + """ + + logo_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="logoCheckedAt"), + pydantic.Field( + alias="logoCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + 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/arcmira/types/topic_page_response_products_item_sentiment.py b/src/arcmira/types/topic_page_response_products_item_sentiment.py new file mode 100644 index 0000000..80ebc7e --- /dev/null +++ b/src/arcmira/types/topic_page_response_products_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseProductsItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/topic_page_response_related_topics_item.py b/src/arcmira/types/topic_page_response_related_topics_item.py new file mode 100644 index 0000000..ce6e123 --- /dev/null +++ b/src/arcmira/types/topic_page_response_related_topics_item.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 .topic_page_response_related_topics_item_sentiment import TopicPageResponseRelatedTopicsItemSentiment + + +class TopicPageResponseRelatedTopicsItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Topic name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: TopicPageResponseRelatedTopicsItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + 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/arcmira/types/topic_page_response_related_topics_item_sentiment.py b/src/arcmira/types/topic_page_response_related_topics_item_sentiment.py new file mode 100644 index 0000000..558b77e --- /dev/null +++ b/src/arcmira/types/topic_page_response_related_topics_item_sentiment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseRelatedTopicsItemSentiment = typing.Union[ + typing.Literal["positive", "neutral", "negative"], typing.Any +] diff --git a/src/arcmira/types/topic_page_response_stats.py b/src/arcmira/types/topic_page_response_stats.py new file mode 100644 index 0000000..c960c72 --- /dev/null +++ b/src/arcmira/types/topic_page_response_stats.py @@ -0,0 +1,94 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class TopicPageResponseStats(UniversalBaseModel): + """ + Headline numbers and section totals for the topic. + """ + + velocity: typing.Optional[int] = pydantic.Field(default=None) + """ + Mentions in the last 7 days. Null when the plan hides it. + """ + + velocity90d: typing.Optional[int] = pydantic.Field(default=None) + """ + Mentions in the last 90 days. Null when the plan hides it. + """ + + sentiment: float = pydantic.Field() + """ + Reserved. Always 0. + """ + + reach: str = pydantic.Field() + """ + Total views as display text, e.g. 1.2M. + """ + + reach_raw: typing_extensions.Annotated[ + float, + FieldMetadata(alias="reachRaw"), + pydantic.Field(alias="reachRaw", description="Total views of the media that mention the topic."), + ] + """ + Total views of the media that mention the topic. + """ + + total: int = pydantic.Field() + """ + Total media that mention the topic. + """ + + latest_media_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="latestMediaAt"), + pydantic.Field( + alias="latestMediaAt", + description="Newest publish date among the counted media. Null when none. The freshness gate does not withhold it.", + ), + ] = None + """ + Newest publish date among the counted media. Null when none. The freshness gate does not withhold it. + """ + + people: int = pydantic.Field() + """ + Total people who appeared in media with the topic. + """ + + organizations: int = pydantic.Field() + """ + Total co-occurring organizations. + """ + + products: int = pydantic.Field() + """ + Total co-occurring products. + """ + + topics: int = pydantic.Field() + """ + Total related topics. + """ + + channels: int = pydantic.Field() + """ + Total channels that mention the topic. + """ + + 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/arcmira/types/topic_page_response_voices_item.py b/src/arcmira/types/topic_page_response_voices_item.py new file mode 100644 index 0000000..338e610 --- /dev/null +++ b/src/arcmira/types/topic_page_response_voices_item.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .topic_page_response_voices_item_role import TopicPageResponseVoicesItemRole +from .topic_page_response_voices_item_sentiment import TopicPageResponseVoicesItemSentiment + + +class TopicPageResponseVoicesItem(UniversalBaseModel): + name: str = pydantic.Field() + """ + Person name. + """ + + count: typing.Optional[int] = pydantic.Field(default=None) + """ + Media shared with the entity. Null when the plan hides counts (_meta.showingFullData false). + """ + + sentiment: TopicPageResponseVoicesItemSentiment = pydantic.Field() + """ + Average sentiment label. Values: positive (average above 0.2), negative (below -0.2), neutral (otherwise, or no score). + """ + + image_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageUrl"), + pydantic.Field(alias="imageUrl", description="Person image URL. Null until an image has been resolved."), + ] = None + """ + Person image URL. Null until an image has been resolved. + """ + + image_checked_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="imageCheckedAt"), + pydantic.Field( + alias="imageCheckedAt", description="When the image pipeline last checked this entity. Null until checked." + ), + ] = None + """ + When the image pipeline last checked this entity. Null until checked. + """ + + role: TopicPageResponseVoicesItemRole = pydantic.Field() + """ + Always Commentator. + """ + + 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/arcmira/types/topic_page_response_voices_item_role.py b/src/arcmira/types/topic_page_response_voices_item_role.py new file mode 100644 index 0000000..29d11e0 --- /dev/null +++ b/src/arcmira/types/topic_page_response_voices_item_role.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseVoicesItemRole = typing.Union[typing.Literal["Commentator"], typing.Any] diff --git a/src/arcmira/types/topic_page_response_voices_item_sentiment.py b/src/arcmira/types/topic_page_response_voices_item_sentiment.py new file mode 100644 index 0000000..060b2bd --- /dev/null +++ b/src/arcmira/types/topic_page_response_voices_item_sentiment.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TopicPageResponseVoicesItemSentiment = typing.Union[typing.Literal["positive", "neutral", "negative"], typing.Any] diff --git a/src/arcmira/types/tracker.py b/src/arcmira/types/tracker.py new file mode 100644 index 0000000..e8a3998 --- /dev/null +++ b/src/arcmira/types/tracker.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class Tracker(UniversalBaseModel): + id: str = pydantic.Field() + """ + Tracker id in the form "trk_{hex}". + """ + + entity_name: typing_extensions.Annotated[ + str, + FieldMetadata(alias="entityName"), + pydantic.Field(alias="entityName", description="The tracked entity name, as submitted."), + ] + """ + The tracked entity name, as submitted. + """ + + entity_type: typing_extensions.Annotated[ + str, + FieldMetadata(alias="entityType"), + pydantic.Field( + alias="entityType", + description="The tracked entity type. Values: person, organization, product, topic, channel.", + ), + ] + """ + The tracked entity type. Values: person, organization, product, topic, channel. + """ + + display_name: typing_extensions.Annotated[ + str, + FieldMetadata(alias="displayName"), + pydantic.Field( + alias="displayName", description="User-facing display name. Falls back to entityName when not customized." + ), + ] + """ + User-facing display name. Falls back to entityName when not customized. + """ + + notify_email: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="notifyEmail"), + pydantic.Field( + alias="notifyEmail", description="True when this tracker delivers by email (default true at creation)." + ), + ] + """ + True when this tracker delivers by email (default true at creation). + """ + + notify_webhook: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="notifyWebhook"), + pydantic.Field( + alias="notifyWebhook", description="True when this tracker has a per-tracker webhook override enabled." + ), + ] + """ + True when this tracker has a per-tracker webhook override enabled. + """ + + notify_slack: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="notifySlack"), + pydantic.Field( + alias="notifySlack", description="True when this tracker has a per-tracker Slack override enabled." + ), + ] + """ + True when this tracker has a per-tracker Slack override enabled. + """ + + webhook_url: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="webhookUrl"), + pydantic.Field( + alias="webhookUrl", + description="Per-tracker webhook destination override. Null when the tracker uses its monitor's delivery settings.", + ), + ] = None + """ + Per-tracker webhook destination override. Null when the tracker uses its monitor's delivery settings. + """ + + slack_channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="slackChannelId"), + pydantic.Field(alias="slackChannelId", description="Per-tracker Slack channel override. Null when not set."), + ] = None + """ + Per-tracker Slack channel override. Null when not set. + """ + + slack_integration_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="slackIntegrationId"), + pydantic.Field( + alias="slackIntegrationId", description="Per-tracker Slack integration override. Null when not set." + ), + ] = None + """ + Per-tracker Slack integration override. Null when not set. + """ + + filters: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Optional matching filters as submitted. Null when none were set. + """ + + is_paused: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="isPaused"), + pydantic.Field(alias="isPaused", description="True when the tracker is paused."), + ] + """ + True when the tracker is paused. + """ + + paused_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="pausedAt"), + pydantic.Field(alias="pausedAt", description="When the tracker was paused. Null unless paused."), + ] = None + """ + When the tracker was paused. Null unless paused. + """ + + last_notified_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="lastNotifiedAt"), + pydantic.Field( + alias="lastNotifiedAt", description="When the tracker last produced an alert. Null until the first alert." + ), + ] = None + """ + When the tracker last produced an alert. Null until the first alert. + """ + + created_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="createdAt"), + pydantic.Field(alias="createdAt", description="When the tracker was created."), + ] + """ + When the tracker was created. + """ + + updated_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="updatedAt"), + pydantic.Field(alias="updatedAt", description="When the tracker was last updated."), + ] = None + """ + When the tracker was last updated. + """ + + monitor_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="monitorId"), + pydantic.Field( + alias="monitorId", description="The monitor this tracker belongs to. Absent for standalone trackers." + ), + ] = None + """ + The monitor this tracker belongs to. Absent for standalone trackers. + """ + + email_delivery_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="emailDeliveryCount"), + pydantic.Field(alias="emailDeliveryCount", description="Email deliveries in the current billing period."), + ] + """ + Email deliveries in the current billing period. + """ + + webhook_delivery_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="webhookDeliveryCount"), + pydantic.Field(alias="webhookDeliveryCount", description="Webhook deliveries in the current billing period."), + ] + """ + Webhook deliveries in the current billing period. + """ + + slack_delivery_count: typing_extensions.Annotated[ + int, + FieldMetadata(alias="slackDeliveryCount"), + pydantic.Field(alias="slackDeliveryCount", description="Slack deliveries in the current billing period."), + ] + """ + Slack deliveries in the current billing period. + """ + + 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/arcmira/types/tracker_list_response.py b/src/arcmira/types/tracker_list_response.py new file mode 100644 index 0000000..224ab6e --- /dev/null +++ b/src/arcmira/types/tracker_list_response.py @@ -0,0 +1,28 @@ +# 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 .tracker import Tracker + + +class TrackerListResponse(UniversalBaseModel): + trackers: typing.List[Tracker] = pydantic.Field() + """ + All trackers for the account, newest first. + """ + + count: int = pydantic.Field() + """ + Number of trackers returned. + """ + + 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/arcmira/types/tracker_mutation_response.py b/src/arcmira/types/tracker_mutation_response.py new file mode 100644 index 0000000..807cd85 --- /dev/null +++ b/src/arcmira/types/tracker_mutation_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 .tracker import Tracker + + +class TrackerMutationResponse(UniversalBaseModel): + tracker: Tracker + message: str = pydantic.Field() + """ + Human-readable confirmation. + """ + + 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/arcmira/types/transcript_edit_submitted_response.py b/src/arcmira/types/transcript_edit_submitted_response.py new file mode 100644 index 0000000..2f12011 --- /dev/null +++ b/src/arcmira/types/transcript_edit_submitted_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 .transcript_edit_submitted_response_edit import TranscriptEditSubmittedResponseEdit + + +class TranscriptEditSubmittedResponse(UniversalBaseModel): + edit: TranscriptEditSubmittedResponseEdit = pydantic.Field() + """ + The stored pending edit. It replaces any pending edit you had on the same line. + """ + + 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/arcmira/types/transcript_edit_submitted_response_edit.py b/src/arcmira/types/transcript_edit_submitted_response_edit.py new file mode 100644 index 0000000..f9dd8ed --- /dev/null +++ b/src/arcmira/types/transcript_edit_submitted_response_edit.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcript_edit_submitted_response_edit_status import TranscriptEditSubmittedResponseEditStatus + + +class TranscriptEditSubmittedResponseEdit(UniversalBaseModel): + """ + The stored pending edit. It replaces any pending edit you had on the same line. + """ + + id: int = pydantic.Field() + """ + Pending row id. Withdraw it with DELETE /v1/transcripts/{video_id}/edits/{id}. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + segment_index: typing_extensions.Annotated[ + int, + FieldMetadata(alias="segmentIndex"), + pydantic.Field(alias="segmentIndex", description="The line index the edit targets."), + ] + """ + The line index the edit targets. + """ + + corrected_text: typing_extensions.Annotated[ + str, + FieldMetadata(alias="correctedText"), + pydantic.Field(alias="correctedText", description="The corrected line text as stored, trimmed."), + ] + """ + The corrected line text as stored, trimmed. + """ + + status: TranscriptEditSubmittedResponseEditStatus = pydantic.Field() + """ + Review status. Always pending on submit. + """ + + 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/arcmira/types/transcript_edit_submitted_response_edit_status.py b/src/arcmira/types/transcript_edit_submitted_response_edit_status.py new file mode 100644 index 0000000..d24d83d --- /dev/null +++ b/src/arcmira/types/transcript_edit_submitted_response_edit_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptEditSubmittedResponseEditStatus = typing.Union[ + typing.Literal["pending", "approved", "rejected", "withdrawn"], typing.Any +] diff --git a/src/arcmira/types/transcript_pending.py b/src/arcmira/types/transcript_pending.py new file mode 100644 index 0000000..cb79487 --- /dev/null +++ b/src/arcmira/types/transcript_pending.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 .transcript_pending_premium_job import TranscriptPendingPremiumJob +from .transcript_pending_quality import TranscriptPendingQuality + + +class TranscriptPending(UniversalBaseModel): + quality: TranscriptPendingQuality + premium_job: TranscriptPendingPremiumJob + status_url: str + next_poll_seconds: float + + 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/arcmira/types/transcript_pending_premium_job.py b/src/arcmira/types/transcript_pending_premium_job.py new file mode 100644 index 0000000..475fe5c --- /dev/null +++ b/src/arcmira/types/transcript_pending_premium_job.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 TranscriptPendingPremiumJob(UniversalBaseModel): + job_id: str + status: str + next_poll_seconds: float + eta_seconds: typing.Optional[float] = 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/arcmira/types/transcript_pending_quality.py b/src/arcmira/types/transcript_pending_quality.py new file mode 100644 index 0000000..dbcfea9 --- /dev/null +++ b/src/arcmira/types/transcript_pending_quality.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPendingQuality = typing.Union[typing.Literal["premium"], typing.Any] diff --git a/src/arcmira/types/transcript_purchase_quote.py b/src/arcmira/types/transcript_purchase_quote.py new file mode 100644 index 0000000..55e4cca --- /dev/null +++ b/src/arcmira/types/transcript_purchase_quote.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 .transcript_purchase_quote_billing_scope import TranscriptPurchaseQuoteBillingScope +from .transcript_purchase_quote_charge import TranscriptPurchaseQuoteCharge +from .transcript_quote import TranscriptQuote + + +class TranscriptPurchaseQuote(UniversalBaseModel): + video_id: str + duration_seconds: float + billing_scope: TranscriptPurchaseQuoteBillingScope + owned: bool + eligible: bool + quote: TranscriptQuote + charge: TranscriptPurchaseQuoteCharge + credits_per_row: float + max_on_demand_cents: float + on_demand_cents_per_unit: float + prepare_url: str + refund_policy: str + + 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/arcmira/types/transcript_purchase_quote_billing_scope.py b/src/arcmira/types/transcript_purchase_quote_billing_scope.py new file mode 100644 index 0000000..9185db7 --- /dev/null +++ b/src/arcmira/types/transcript_purchase_quote_billing_scope.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPurchaseQuoteBillingScope = typing.Union[typing.Literal["full_video"], typing.Any] diff --git a/src/arcmira/types/transcript_purchase_quote_charge.py b/src/arcmira/types/transcript_purchase_quote_charge.py new file mode 100644 index 0000000..ae9babc --- /dev/null +++ b/src/arcmira/types/transcript_purchase_quote_charge.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 +from .transcript_purchase_quote_charge_unit import TranscriptPurchaseQuoteChargeUnit + + +class TranscriptPurchaseQuoteCharge(UniversalBaseModel): + unit: TranscriptPurchaseQuoteChargeUnit + amount: float + + 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/arcmira/types/transcript_purchase_quote_charge_unit.py b/src/arcmira/types/transcript_purchase_quote_charge_unit.py new file mode 100644 index 0000000..f24a10e --- /dev/null +++ b/src/arcmira/types/transcript_purchase_quote_charge_unit.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPurchaseQuoteChargeUnit = typing.Union[typing.Literal["rows", "credits"], typing.Any] diff --git a/src/arcmira/types/transcript_quote.py b/src/arcmira/types/transcript_quote.py new file mode 100644 index 0000000..1781ac2 --- /dev/null +++ b/src/arcmira/types/transcript_quote.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 TranscriptQuote(UniversalBaseModel): + quarters: int = pydantic.Field() + """ + Number of 15-minute blocks in the video, ceiling'd, minimum 1. + """ + + rows: int = pydantic.Field() + """ + Total unlock cost in rows: 75 rows per 15-minute block. + """ + + 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/arcmira/types/transcript_response.py b/src/arcmira/types/transcript_response.py new file mode 100644 index 0000000..ad4dabc --- /dev/null +++ b/src/arcmira/types/transcript_response.py @@ -0,0 +1,98 @@ +# 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 .caption_track import CaptionTrack +from .transcript_response_access import TranscriptResponseAccess +from .transcript_response_lines_item import TranscriptResponseLinesItem +from .transcript_response_paragraphs_item import TranscriptResponseParagraphsItem +from .transcript_response_premium_job import TranscriptResponsePremiumJob +from .transcript_response_quality import TranscriptResponseQuality +from .transcript_response_range import TranscriptResponseRange +from .transcript_response_source import TranscriptResponseSource +from .transcript_response_speakers_item import TranscriptResponseSpeakersItem +from .transcript_video import TranscriptVideo + + +class TranscriptResponse(UniversalBaseModel): + video: TranscriptVideo + quality: TranscriptResponseQuality = pydantic.Field() + """ + The requested quality. Premium is served only with an owned unlock; it never falls back to captions. + """ + + source: TranscriptResponseSource = pydantic.Field() + """ + Public source class. creator_captions were written or approved by the channel, third_party_quick are YouTube automatic captions, arcmira_premium is our own diarized transcript. + """ + + language: str = pydantic.Field() + """ + The resolved track code, asr-en style when the track is automatic. + """ + + languages: typing.List[CaptionTrack] = pydantic.Field() + """ + Every caption track the video offers. Empty when we did not list them on this call. + """ + + lines: typing.Optional[typing.List[TranscriptResponseLinesItem]] = pydantic.Field(default=None) + """ + Present when timestamps is true. Cite start with watch_url. + """ + + paragraphs: typing.Optional[typing.List[TranscriptResponseParagraphsItem]] = pydantic.Field(default=None) + """ + Present when timestamps is false. Lines joined on speaker changes for Premium and on sentence boundaries for captions. + """ + + speakers: typing.Optional[typing.List[TranscriptResponseSpeakersItem]] = pydantic.Field(default=None) + """ + Premium only. Speaker identification is right most of the time and wrong sometimes; say it came from Arcmira when a name matters. + """ + + revision: typing.Optional[str] = pydantic.Field(default=None) + """ + Premium reads only. Opaque id of the transcript you were served, the approved corrections on it, and who speaks each line. Echo it on every correction; a 409 means it changed underneath you, so read again. + """ + + range: typing.Optional[TranscriptResponseRange] = pydantic.Field(default=None) + """ + Echoed when you sent start and end. Lines overlapping the window are returned. On captions only the window is billed; Premium retrieval is free. + """ + + rows_billed: int = pydantic.Field() + """ + Rows this call charged. 0 on a repeat of the same video, quality, language, and range inside the 7 day dedupe window, and always 0 on Premium retrieval. + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + When the transcript was produced. + """ + + premium_job: typing.Optional[TranscriptResponsePremiumJob] = pydantic.Field(default=None) + """ + Reserved for job metadata. Pending Premium retrieval uses its separate 202 response. + """ + + access: typing.Optional[TranscriptResponseAccess] = pydantic.Field(default=None) + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. On Premium it is the diarization disclosure verbatim. + """ + + 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/arcmira/types/transcript_response_access.py b/src/arcmira/types/transcript_response_access.py new file mode 100644 index 0000000..c32adf5 --- /dev/null +++ b/src/arcmira/types/transcript_response_access.py @@ -0,0 +1,68 @@ +# 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 .transcript_response_access_gate import TranscriptResponseAccessGate +from .transcript_response_access_reason import TranscriptResponseAccessReason +from .transcript_response_access_type import TranscriptResponseAccessType +from .transcript_response_access_unlock import TranscriptResponseAccessUnlock + + +class TranscriptResponseAccess(UniversalBaseModel): + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + type: TranscriptResponseAccessType = pydantic.Field() + """ + The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling. + """ + + code: str = pydantic.Field() + """ + The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first. + """ + + reason: typing.Optional[TranscriptResponseAccessReason] = pydantic.Field(default=None) + """ + Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable. + """ + + message: str = pydantic.Field() + """ + One plain line. Names the fix or the unlock. + """ + + param: typing.Optional[str] = pydantic.Field(default=None) + """ + The query or body parameter the gate refused, when one did. + """ + + gate: typing.Optional[TranscriptResponseAccessGate] = pydantic.Field(default=None) + """ + Which boundary refused. Present on every gate error; switch on it without parsing the message. + """ + + unlock: typing.Optional[TranscriptResponseAccessUnlock] = pydantic.Field(default=None) + """ + How to lift the gate. Present when the gate has an unlock. + """ + + retry_after_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Present on rate gates. Mirrors the Retry-After header. + """ + + doc_url: str + request_id: str + + 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/arcmira/types/transcript_response_access_gate.py b/src/arcmira/types/transcript_response_access_gate.py new file mode 100644 index 0000000..3a3ae4b --- /dev/null +++ b/src/arcmira/types/transcript_response_access_gate.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptResponseAccessGate = typing.Union[ + typing.Literal["rows", "key", "plan", "freshness", "exposure_law", "rate", "pagination"], typing.Any +] diff --git a/src/arcmira/types/transcript_response_access_reason.py b/src/arcmira/types/transcript_response_access_reason.py new file mode 100644 index 0000000..22c39e1 --- /dev/null +++ b/src/arcmira/types/transcript_response_access_reason.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptResponseAccessReason = typing.Union[typing.Literal["no_credential", "invalid", "revoked"], typing.Any] diff --git a/src/arcmira/types/transcript_response_access_type.py b/src/arcmira/types/transcript_response_access_type.py new file mode 100644 index 0000000..db63b47 --- /dev/null +++ b/src/arcmira/types/transcript_response_access_type.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptResponseAccessType = typing.Union[ + typing.Literal[ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error", + ], + typing.Any, +] diff --git a/src/arcmira/types/transcript_response_access_unlock.py b/src/arcmira/types/transcript_response_access_unlock.py new file mode 100644 index 0000000..6363a09 --- /dev/null +++ b/src/arcmira/types/transcript_response_access_unlock.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 +from .transcript_response_access_unlock_action import TranscriptResponseAccessUnlockAction + + +class TranscriptResponseAccessUnlock(UniversalBaseModel): + """ + How to lift the gate. Present when the gate has an unlock. + """ + + tier: str = pydantic.Field() + """ + The plan that lifts the gate. + """ + + url: str = pydantic.Field() + """ + Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim. + """ + + offer: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Reserved for the agent-discount offer. Always null today. + """ + + action: typing.Optional[TranscriptResponseAccessUnlockAction] = pydantic.Field(default=None) + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + 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/arcmira/types/transcript_response_access_unlock_action.py b/src/arcmira/types/transcript_response_access_unlock_action.py new file mode 100644 index 0000000..93ca09c --- /dev/null +++ b/src/arcmira/types/transcript_response_access_unlock_action.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 TranscriptResponseAccessUnlockAction(UniversalBaseModel): + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + kind: str = pydantic.Field() + """ + What the call does. send_signup_code sends a verification code to an address for an account key. + """ + + method: str = pydantic.Field() + """ + HTTP method to use. + """ + + url: str = pydantic.Field() + """ + Absolute endpoint carrying its ?src= attribution. Call it verbatim. + """ + + 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/arcmira/types/transcript_response_lines_item.py b/src/arcmira/types/transcript_response_lines_item.py new file mode 100644 index 0000000..1462344 --- /dev/null +++ b/src/arcmira/types/transcript_response_lines_item.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 + + +class TranscriptResponseLinesItem(UniversalBaseModel): + start: float = pydantic.Field() + """ + Line start in seconds from the beginning of the video. + """ + + end: float = pydantic.Field() + """ + Line end in seconds. + """ + + text: str + speaker: typing.Optional[int] = pydantic.Field(default=None) + """ + The person saying this line, present on every Premium line. Join it against speakers[].id. + """ + + index: typing.Optional[int] = pydantic.Field(default=None) + """ + Line index, present on every Premium line. Echo it as anchor.segmentIndex when you correct the line. + """ + + 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/arcmira/types/transcript_response_paragraphs_item.py b/src/arcmira/types/transcript_response_paragraphs_item.py new file mode 100644 index 0000000..0f47a3f --- /dev/null +++ b/src/arcmira/types/transcript_response_paragraphs_item.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 TranscriptResponseParagraphsItem(UniversalBaseModel): + start: float = pydantic.Field() + """ + Paragraph start in seconds. + """ + + text: str + speaker: typing.Optional[int] = 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/arcmira/types/transcript_response_premium_job.py b/src/arcmira/types/transcript_response_premium_job.py new file mode 100644 index 0000000..79fffe7 --- /dev/null +++ b/src/arcmira/types/transcript_response_premium_job.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 + + +class TranscriptResponsePremiumJob(UniversalBaseModel): + """ + Reserved for job metadata. Pending Premium retrieval uses its separate 202 response. + """ + + job_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Transcription request id. Poll it with GET /v1/transcriptions/{id}. + """ + + status: str = pydantic.Field() + """ + Pipeline status at submit time. + """ + + next_poll_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Seconds to wait before polling again. + """ + + eta_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Estimated seconds until the Premium transcript is ready. + """ + + 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/arcmira/types/transcript_response_quality.py b/src/arcmira/types/transcript_response_quality.py new file mode 100644 index 0000000..e547545 --- /dev/null +++ b/src/arcmira/types/transcript_response_quality.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptResponseQuality = typing.Union[typing.Literal["captions", "premium"], typing.Any] diff --git a/src/arcmira/types/transcript_response_range.py b/src/arcmira/types/transcript_response_range.py new file mode 100644 index 0000000..bc67bb1 --- /dev/null +++ b/src/arcmira/types/transcript_response_range.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 + + +class TranscriptResponseRange(UniversalBaseModel): + """ + Echoed when you sent start and end. Lines overlapping the window are returned. On captions only the window is billed; Premium retrieval is free. + """ + + start: float + end: float + + 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/arcmira/types/transcript_response_source.py b/src/arcmira/types/transcript_response_source.py new file mode 100644 index 0000000..b8df025 --- /dev/null +++ b/src/arcmira/types/transcript_response_source.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptResponseSource = typing.Union[ + typing.Literal["creator_captions", "third_party_quick", "arcmira_premium"], typing.Any +] diff --git a/src/arcmira/types/transcript_response_speakers_item.py b/src/arcmira/types/transcript_response_speakers_item.py new file mode 100644 index 0000000..32115c0 --- /dev/null +++ b/src/arcmira/types/transcript_response_speakers_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 TranscriptResponseSpeakersItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Speaker id, numbered in the order people first speak. Only meaningful with this read and its revision. + """ + + name: str = pydantic.Field() + """ + The identified person, or Speaker 1, Speaker 2 and so on for a voice nobody has identified yet. + """ + + entity_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Raw entity id of the identified person. Null when the speaker is unidentified. + """ + + confidence: typing.Optional[str] = pydantic.Field(default=None) + """ + high when the name was reviewed, low when it is your own identification still awaiting review, null when nobody is identified. + """ + + 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/arcmira/types/transcript_result.py b/src/arcmira/types/transcript_result.py new file mode 100644 index 0000000..d5a2ec0 --- /dev/null +++ b/src/arcmira/types/transcript_result.py @@ -0,0 +1,71 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .caption_track import CaptionTrack +from .transcript_pending_premium_job import TranscriptPendingPremiumJob +from .transcript_pending_quality import TranscriptPendingQuality +from .transcript_response_access import TranscriptResponseAccess +from .transcript_response_lines_item import TranscriptResponseLinesItem +from .transcript_response_paragraphs_item import TranscriptResponseParagraphsItem +from .transcript_response_premium_job import TranscriptResponsePremiumJob +from .transcript_response_quality import TranscriptResponseQuality +from .transcript_response_range import TranscriptResponseRange +from .transcript_response_source import TranscriptResponseSource +from .transcript_response_speakers_item import TranscriptResponseSpeakersItem +from .transcript_video import TranscriptVideo + + +class TranscriptResult_Ready(UniversalBaseModel): + state: typing.Literal["ready"] = "ready" + video: TranscriptVideo + quality: TranscriptResponseQuality + source: TranscriptResponseSource + language: str + languages: typing.List[CaptionTrack] + lines: typing.Optional[typing.List[TranscriptResponseLinesItem]] = None + paragraphs: typing.Optional[typing.List[TranscriptResponseParagraphsItem]] = None + speakers: typing.Optional[typing.List[TranscriptResponseSpeakersItem]] = None + revision: typing.Optional[str] = None + range: typing.Optional[TranscriptResponseRange] = None + rows_billed: int + as_of: typing.Optional[str] = None + premium_job: typing.Optional[TranscriptResponsePremiumJob] = None + access: typing.Optional[TranscriptResponseAccess] = None + note: str + + 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 + + +class TranscriptResult_Pending(UniversalBaseModel): + state: typing.Literal["pending"] = "pending" + quality: TranscriptPendingQuality + premium_job: TranscriptPendingPremiumJob + status_url: str + next_poll_seconds: float + + 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 + + +TranscriptResult = typing_extensions.Annotated[ + typing.Union[TranscriptResult_Ready, TranscriptResult_Pending], pydantic.Field(discriminator="state") +] diff --git a/src/arcmira/types/transcript_search_chunk.py b/src/arcmira/types/transcript_search_chunk.py new file mode 100644 index 0000000..3e63158 --- /dev/null +++ b/src/arcmira/types/transcript_search_chunk.py @@ -0,0 +1,162 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .named_entity_ref import NamedEntityRef + + +class TranscriptSearchChunk(UniversalBaseModel): + id: str = pydantic.Field() + """ + Search index chunk id. Opaque. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + channel_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelId"), + pydantic.Field(alias="channelId", description="YouTube channel id of the source channel."), + ] = None + """ + YouTube channel id of the source channel. + """ + + channel_name: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelName"), + pydantic.Field(alias="channelName", description="Source channel name."), + ] = None + """ + Source channel name. + """ + + channel_page: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="channelPage"), + pydantic.Field( + alias="channelPage", + description="The channel's page on arcmira.com, absolute. Null when no channel is known.", + ), + ] = None + """ + The channel's page on arcmira.com, absolute. Null when no channel is known. + """ + + video_title: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="videoTitle"), + pydantic.Field(alias="videoTitle", description="Video title."), + ] = None + """ + Video title. + """ + + speakers: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Speaker names identified on this slice, when known. + """ + + source: typing.Optional[str] = pydantic.Field(default=None) + """ + Transcript source class: arcmira_premium, creator_captions, or third_party_quick. + """ + + source_label: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="sourceLabel"), + pydantic.Field(alias="sourceLabel", description="Human label for source."), + ] = None + """ + Human label for source. + """ + + published_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="publishedAt"), + pydantic.Field(alias="publishedAt", description="Video publish timestamp. Cite it as the date of the quote."), + ] = None + """ + Video publish timestamp. Cite it as the date of the quote. + """ + + text: str = pydantic.Field() + """ + The spoken slice. Search results include text on every plan within the permitted publication-date window. + """ + + text_withheld: typing_extensions.Annotated[ + typing.Optional[bool], + FieldMetadata(alias="textWithheld"), + pydantic.Field( + alias="textWithheld", + description="Legacy field, no longer set. Since 2026-09-10, transcript search includes spoken text on every plan and limits results by publication date.", + ), + ] = None + """ + Legacy field, no longer set. Since 2026-09-10, transcript search includes spoken text on every plan and limits results by publication date. + """ + + start_seconds: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="startSeconds"), + pydantic.Field(alias="startSeconds", description="Offset of the slice in the video, in seconds."), + ] = None + """ + Offset of the slice in the video, in seconds. + """ + + watch_url: typing_extensions.Annotated[ + str, + FieldMetadata(alias="watchUrl"), + pydantic.Field( + alias="watchUrl", description="Site-relative watch URL with the timestamp, e.g. /watch?v=...&t=4787." + ), + ] + """ + Site-relative watch URL with the timestamp, e.g. /watch?v=...&t=4787. + """ + + cite_line: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="citeLine"), + pydantic.Field(alias="citeLine", description="A ready citation line: title, clock, channel, date."), + ] = None + """ + A ready citation line: title, clock, channel, date. + """ + + score: float = pydantic.Field() + """ + Retrieval score. Higher is a closer match. Not comparable across calls. + """ + + about: typing.Optional[typing.List[NamedEntityRef]] = pydantic.Field(default=None) + """ + Entities the passage is tagged about (excerpt pins, exact-name mentions, ad verdicts). Present on passages served from the spoken index. + """ + + speakers_by: typing.Optional[typing.List[NamedEntityRef]] = pydantic.Field(default=None) + """ + The people speaking in the passage, as ids with names. Present on passages served from the spoken index. + """ + + 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/arcmira/types/transcript_search_response.py b/src/arcmira/types/transcript_search_response.py new file mode 100644 index 0000000..4aaacf2 --- /dev/null +++ b/src/arcmira/types/transcript_search_response.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcript_search_chunk import TranscriptSearchChunk +from .transcript_search_response_access import TranscriptSearchResponseAccess +from .transcript_search_response_filters import TranscriptSearchResponseFilters +from .transcript_search_response_search_index import TranscriptSearchResponseSearchIndex + + +class TranscriptSearchResponse(UniversalBaseModel): + query: str = pydantic.Field() + """ + The q parameter echoed back. + """ + + requested_k: typing_extensions.Annotated[ + int, FieldMetadata(alias="requestedK"), pydantic.Field(alias="requestedK", description="The limit applied.") + ] + """ + The limit applied. + """ + + returned_n: typing_extensions.Annotated[ + int, FieldMetadata(alias="returnedN"), pydantic.Field(alias="returnedN", description="Chunks returned.") + ] + """ + Chunks returned. + """ + + filters: TranscriptSearchResponseFilters + chunks: typing.List[TranscriptSearchChunk] = pydantic.Field() + """ + Ranked slices. Empty means no hit in the shows we index; say so, never search the open web. + """ + + partial: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when some retrieval batches failed and these chunks are what survived. + """ + + failed_batches: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="failedBatches"), + pydantic.Field(alias="failedBatches", description="How many batches failed when partial is true."), + ] = None + """ + How many batches failed when partial is true. + """ + + as_of: typing.Optional[str] = pydantic.Field(default=None) + """ + Newest publishedAt among the chunks. Null when there are none. + """ + + search_index: TranscriptSearchResponseSearchIndex = pydantic.Field() + """ + Health of the search index behind these results. Catalog routes are unaffected by it. + """ + + access: typing.Optional[TranscriptSearchResponseAccess] = pydantic.Field(default=None) + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + note: str = pydantic.Field() + """ + One steering sentence for the agent reading this. + """ + + 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/arcmira/types/transcript_search_response_access.py b/src/arcmira/types/transcript_search_response_access.py new file mode 100644 index 0000000..1029026 --- /dev/null +++ b/src/arcmira/types/transcript_search_response_access.py @@ -0,0 +1,68 @@ +# 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 .transcript_search_response_access_gate import TranscriptSearchResponseAccessGate +from .transcript_search_response_access_reason import TranscriptSearchResponseAccessReason +from .transcript_search_response_access_type import TranscriptSearchResponseAccessType +from .transcript_search_response_access_unlock import TranscriptSearchResponseAccessUnlock + + +class TranscriptSearchResponseAccess(UniversalBaseModel): + """ + The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. + """ + + type: TranscriptSearchResponseAccessType = pydantic.Field() + """ + The error class. It fixes the HTTP status: invalid_request_error 400, authentication_error 401, quota_exceeded 402, permission_error 403, not_found 404, conflict_error 409, rate_limit_error 429, server_error 500. Switch on it for retry and gate handling. + """ + + code: str = pydantic.Field() + """ + The specific condition, stable and snake_case; doc_url anchors on it. x-arcmira-codes on this schema lists every code with its type, gate and meaning. The list is open: new codes may appear inside an existing type, so switch on type and gate first. + """ + + reason: typing.Optional[TranscriptSearchResponseAccessReason] = pydantic.Field(default=None) + """ + Only on invalid_api_key. no_credential: nothing was sent. invalid: a credential was sent and is unknown or malformed. revoked: the key exists and is no longer usable. + """ + + message: str = pydantic.Field() + """ + One plain line. Names the fix or the unlock. + """ + + param: typing.Optional[str] = pydantic.Field(default=None) + """ + The query or body parameter the gate refused, when one did. + """ + + gate: typing.Optional[TranscriptSearchResponseAccessGate] = pydantic.Field(default=None) + """ + Which boundary refused. Present on every gate error; switch on it without parsing the message. + """ + + unlock: typing.Optional[TranscriptSearchResponseAccessUnlock] = pydantic.Field(default=None) + """ + How to lift the gate. Present when the gate has an unlock. + """ + + retry_after_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Present on rate gates. Mirrors the Retry-After header. + """ + + doc_url: str + request_id: str + + 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/arcmira/types/transcript_search_response_access_gate.py b/src/arcmira/types/transcript_search_response_access_gate.py new file mode 100644 index 0000000..f22c81d --- /dev/null +++ b/src/arcmira/types/transcript_search_response_access_gate.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptSearchResponseAccessGate = typing.Union[ + typing.Literal["rows", "key", "plan", "freshness", "exposure_law", "rate", "pagination"], typing.Any +] diff --git a/src/arcmira/types/transcript_search_response_access_reason.py b/src/arcmira/types/transcript_search_response_access_reason.py new file mode 100644 index 0000000..fc1f066 --- /dev/null +++ b/src/arcmira/types/transcript_search_response_access_reason.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptSearchResponseAccessReason = typing.Union[typing.Literal["no_credential", "invalid", "revoked"], typing.Any] diff --git a/src/arcmira/types/transcript_search_response_access_type.py b/src/arcmira/types/transcript_search_response_access_type.py new file mode 100644 index 0000000..b5e49c3 --- /dev/null +++ b/src/arcmira/types/transcript_search_response_access_type.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptSearchResponseAccessType = typing.Union[ + typing.Literal[ + "invalid_request_error", + "authentication_error", + "permission_error", + "quota_exceeded", + "rate_limit_error", + "not_found", + "conflict_error", + "server_error", + ], + typing.Any, +] diff --git a/src/arcmira/types/transcript_search_response_access_unlock.py b/src/arcmira/types/transcript_search_response_access_unlock.py new file mode 100644 index 0000000..da0c7aa --- /dev/null +++ b/src/arcmira/types/transcript_search_response_access_unlock.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 +from .transcript_search_response_access_unlock_action import TranscriptSearchResponseAccessUnlockAction + + +class TranscriptSearchResponseAccessUnlock(UniversalBaseModel): + """ + How to lift the gate. Present when the gate has an unlock. + """ + + tier: str = pydantic.Field() + """ + The plan that lifts the gate. + """ + + url: str = pydantic.Field() + """ + Absolute upgrade or sign-up URL carrying its ?src= attribution. Use it verbatim. + """ + + offer: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Reserved for the agent-discount offer. Always null today. + """ + + action: typing.Optional[TranscriptSearchResponseAccessUnlockAction] = pydantic.Field(default=None) + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + 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/arcmira/types/transcript_search_response_access_unlock_action.py b/src/arcmira/types/transcript_search_response_access_unlock_action.py new file mode 100644 index 0000000..b75c2ec --- /dev/null +++ b/src/arcmira/types/transcript_search_response_access_unlock_action.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 TranscriptSearchResponseAccessUnlockAction(UniversalBaseModel): + """ + The request that lifts the gate with no human, present when the caller can fix this itself. A key gate carries the signup send here; a plan or quota gate has no action and its url is a page a person opens. + """ + + kind: str = pydantic.Field() + """ + What the call does. send_signup_code sends a verification code to an address for an account key. + """ + + method: str = pydantic.Field() + """ + HTTP method to use. + """ + + url: str = pydantic.Field() + """ + Absolute endpoint carrying its ?src= attribution. Call it verbatim. + """ + + 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/arcmira/types/transcript_search_response_filters.py b/src/arcmira/types/transcript_search_response_filters.py new file mode 100644 index 0000000..678ebf5 --- /dev/null +++ b/src/arcmira/types/transcript_search_response_filters.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .named_entity_ref import NamedEntityRef + + +class TranscriptSearchResponseFilters(UniversalBaseModel): + channel_ids: typing_extensions.Annotated[ + typing.List[str], + FieldMetadata(alias="channelIds"), + pydantic.Field( + alias="channelIds", description="Channel ids the search was scoped to, after entity_ids were expanded." + ), + ] + """ + Channel ids the search was scoped to, after entity_ids were expanded. + """ + + entity_ids: typing_extensions.Annotated[ + typing.List[str], + FieldMetadata(alias="entityIds"), + pydantic.Field( + alias="entityIds", + description="Exact explicit entity_ids accepted for this search. Every id was resolved; an unknown id is refused.", + ), + ] + """ + Exact explicit entity_ids accepted for this search. Every id was resolved; an unknown id is refused. + """ + + published_after: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="publishedAfter"), pydantic.Field(alias="publishedAfter") + ] = None + published_before: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="publishedBefore"), pydantic.Field(alias="publishedBefore") + ] = None + about: typing.List[NamedEntityRef] = pydantic.Field() + """ + The about ids, each with its name and type. + """ + + by: typing.List[NamedEntityRef] = pydantic.Field() + """ + The by ids, each with its name and type. + """ + + kind: typing.List[str] = pydantic.Field() + """ + The kind values 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/arcmira/types/transcript_search_response_search_index.py b/src/arcmira/types/transcript_search_response_search_index.py new file mode 100644 index 0000000..d598db2 --- /dev/null +++ b/src/arcmira/types/transcript_search_response_search_index.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 +from .transcript_search_response_search_index_state import TranscriptSearchResponseSearchIndexState + + +class TranscriptSearchResponseSearchIndex(UniversalBaseModel): + """ + Health of the search index behind these results. Catalog routes are unaffected by it. + """ + + state: TranscriptSearchResponseSearchIndexState = pydantic.Field() + """ + Whether every indexed transcript is searchable. catching_up means older transcripts are still being added; note says so when the asked window reaches them. + """ + + missing_before: typing.Optional[str] = pydantic.Field(default=None) + """ + While catching up, transcripts published before this date may be missing from search. + """ + + 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/arcmira/types/transcript_search_response_search_index_state.py b/src/arcmira/types/transcript_search_response_search_index_state.py new file mode 100644 index 0000000..6cd3a52 --- /dev/null +++ b/src/arcmira/types/transcript_search_response_search_index_state.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptSearchResponseSearchIndexState = typing.Union[typing.Literal["live", "catching_up", "unknown"], typing.Any] diff --git a/src/arcmira/types/transcript_settings.py b/src/arcmira/types/transcript_settings.py new file mode 100644 index 0000000..950c9e4 --- /dev/null +++ b/src/arcmira/types/transcript_settings.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 .transcript_settings_quality import TranscriptSettingsQuality + + +class TranscriptSettings(UniversalBaseModel): + """ + What a transcript request that names no parameter of its own receives. + """ + + quality: TranscriptSettingsQuality = pydantic.Field() + """ + Default transcript quality. Platform default: captions. + """ + + language: str = pydantic.Field() + """ + Default comma-separated caption language priority list, tried in order. Platform default: en. + """ + + timestamps: bool = pydantic.Field() + """ + Default for the timestamps parameter. Platform default: true, which returns lines[]. + """ + + 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/arcmira/types/transcript_settings_quality.py b/src/arcmira/types/transcript_settings_quality.py new file mode 100644 index 0000000..7f87569 --- /dev/null +++ b/src/arcmira/types/transcript_settings_quality.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptSettingsQuality = typing.Union[typing.Literal["captions", "premium"], typing.Any] diff --git a/src/arcmira/types/transcript_video.py b/src/arcmira/types/transcript_video.py new file mode 100644 index 0000000..05fe0a2 --- /dev/null +++ b/src/arcmira/types/transcript_video.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 + + +class TranscriptVideo(UniversalBaseModel): + id: str = pydantic.Field() + """ + YouTube video id (11 characters). + """ + + title: str = pydantic.Field() + """ + Video title. Empty when we could not read it. + """ + + channel_id: typing.Optional[str] = pydantic.Field(default=None) + """ + YouTube channel id of the source channel. + """ + + channel_name: typing.Optional[str] = None + published_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Publish timestamp. Cite it as the date of anything you quote. + """ + + duration_seconds: typing.Optional[float] = pydantic.Field(default=None) + """ + Video length in seconds. Null when unknown, which also means the row estimate was unknown. + """ + + watch_url: str = pydantic.Field() + """ + Canonical YouTube watch 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/arcmira/types/transcription_list_response.py b/src/arcmira/types/transcription_list_response.py new file mode 100644 index 0000000..14e650a --- /dev/null +++ b/src/arcmira/types/transcription_list_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 .transcription_list_response_requests_item import TranscriptionListResponseRequestsItem + + +class TranscriptionListResponse(UniversalBaseModel): + requests: typing.List[TranscriptionListResponseRequestsItem] = pydantic.Field() + """ + Your requests in descending creation time and id order, up to the requested limit. + """ + + has_more: bool = pydantic.Field() + """ + True when another page exists in this traversal. + """ + + next_cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Signed continuation for the same filter, limit and credential; null on the last 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/arcmira/types/transcription_list_response_requests_item.py b/src/arcmira/types/transcription_list_response_requests_item.py new file mode 100644 index 0000000..81b3b7d --- /dev/null +++ b/src/arcmira/types/transcription_list_response_requests_item.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 +from .transcription_request import TranscriptionRequest + + +class TranscriptionListResponseRequestsItem(TranscriptionRequest): + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Video title for display. Null when unknown. + """ + + 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/arcmira/types/transcription_request.py b/src/arcmira/types/transcription_request.py new file mode 100644 index 0000000..a8beabe --- /dev/null +++ b/src/arcmira/types/transcription_request.py @@ -0,0 +1,113 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcription_request_charge import TranscriptionRequestCharge +from .transcription_request_quote import TranscriptionRequestQuote +from .transcription_request_stage import TranscriptionRequestStage +from .transcription_request_state import TranscriptionRequestState +from .transcription_request_status import TranscriptionRequestStatus + + +class TranscriptionRequest(UniversalBaseModel): + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Transcription request id (UUID). Null only in the degenerate submit response for a video you already own that has no request history. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + status: TranscriptionRequestStatus = pydantic.Field() + """ + Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked). + """ + + state: TranscriptionRequestState + charge: typing.Optional[TranscriptionRequestCharge] = pydantic.Field(default=None) + """ + Accepted charge units. Present on durable purchases; absent only on legacy requests. + """ + + stage: typing.Optional[TranscriptionRequestStage] = pydantic.Field(default=None) + """ + User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses. + """ + + quote: TranscriptionRequestQuote = pydantic.Field() + """ + What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free. + """ + + eta_seconds: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="etaSeconds"), + pydantic.Field( + alias="etaSeconds", + description="Estimated SECONDS until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight.", + ), + ] = None + """ + Estimated SECONDS until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight. + """ + + next_poll_seconds: typing_extensions.Annotated[ + typing.Optional[int], + FieldMetadata(alias="nextPollSeconds"), + pydantic.Field( + alias="nextPollSeconds", + description="Recommended SECONDS to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight.", + ), + ] = None + """ + Recommended SECONDS to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight. + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Failure reason. Only present when status is failed or refunded. + """ + + refunded: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when the charged rows were returned. Only present when status is failed or refunded. + """ + + created_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="createdAt"), + pydantic.Field(alias="createdAt", description="When the request was submitted."), + ] + """ + When the request was submitted. + """ + + completed_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="completedAt"), + pydantic.Field( + alias="completedAt", description="When the request reached a terminal status. Absent while in flight." + ), + ] = None + """ + When the request reached a terminal status. Absent while in flight. + """ + + 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/arcmira/types/transcription_request_charge.py b/src/arcmira/types/transcription_request_charge.py new file mode 100644 index 0000000..291966a --- /dev/null +++ b/src/arcmira/types/transcription_request_charge.py @@ -0,0 +1,26 @@ +# 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 .transcription_request_charge_unit import TranscriptionRequestChargeUnit + + +class TranscriptionRequestCharge(UniversalBaseModel): + """ + Accepted charge units. Present on durable purchases; absent only on legacy requests. + """ + + unit: TranscriptionRequestChargeUnit + amount: float + credits_per_row: float + + 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/arcmira/types/transcription_request_charge_unit.py b/src/arcmira/types/transcription_request_charge_unit.py new file mode 100644 index 0000000..3518009 --- /dev/null +++ b/src/arcmira/types/transcription_request_charge_unit.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptionRequestChargeUnit = typing.Union[typing.Literal["rows", "credits"], typing.Any] diff --git a/src/arcmira/types/transcription_request_quote.py b/src/arcmira/types/transcription_request_quote.py new file mode 100644 index 0000000..bc3ad53 --- /dev/null +++ b/src/arcmira/types/transcription_request_quote.py @@ -0,0 +1,31 @@ +# 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 TranscriptionRequestQuote(UniversalBaseModel): + """ + What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free. + """ + + quarters: int = pydantic.Field() + """ + Number of 15-minute blocks in the video, ceiling'd, minimum 1. + """ + + rows: int = pydantic.Field() + """ + Total unlock cost in rows: 75 rows per 15-minute block. + """ + + 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/arcmira/types/transcription_request_stage.py b/src/arcmira/types/transcription_request_stage.py new file mode 100644 index 0000000..5285152 --- /dev/null +++ b/src/arcmira/types/transcription_request_stage.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptionRequestStage = typing.Union[typing.Literal["queued", "transcribing", "analyzing"], typing.Any] diff --git a/src/arcmira/types/transcription_request_state.py b/src/arcmira/types/transcription_request_state.py new file mode 100644 index 0000000..a237ad9 --- /dev/null +++ b/src/arcmira/types/transcription_request_state.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptionRequestState = typing.Union[typing.Literal["pending", "ready", "failed", "refunded"], typing.Any] diff --git a/src/arcmira/types/transcription_request_status.py b/src/arcmira/types/transcription_request_status.py new file mode 100644 index 0000000..c2a4eaa --- /dev/null +++ b/src/arcmira/types/transcription_request_status.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptionRequestStatus = typing.Union[ + typing.Literal[ + "queued", "downloading", "transcribing", "analyzing", "complete", "failed", "refund_pending", "refunded" + ], + typing.Any, +] diff --git a/src/arcmira/types/transcription_submit_response.py b/src/arcmira/types/transcription_submit_response.py new file mode 100644 index 0000000..e7f8562 --- /dev/null +++ b/src/arcmira/types/transcription_submit_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcription_request import TranscriptionRequest + + +class TranscriptionSubmitResponse(UniversalBaseModel): + request: TranscriptionRequest + existing: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when an in-flight (or already-satisfied) request for the same video was returned instead of creating a new one. + """ + + over_limit: typing_extensions.Annotated[ + typing.Optional[bool], + FieldMetadata(alias="overLimit"), + pydantic.Field( + alias="overLimit", + description="Only present (true) when this purchase consumed the rest of the included row allocation.", + ), + ] = None + """ + Only present (true) when this purchase consumed the rest of the included row allocation. + """ + + 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/arcmira/types/video_captions_response.py b/src/arcmira/types/video_captions_response.py new file mode 100644 index 0000000..8fb8282 --- /dev/null +++ b/src/arcmira/types/video_captions_response.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 +from .caption_track import CaptionTrack +from .transcript_video import TranscriptVideo + + +class VideoCaptionsResponse(UniversalBaseModel): + video: TranscriptVideo + languages: typing.List[CaptionTrack] = pydantic.Field() + """ + Every caption track the video offers. Pass a code, or a comma-separated priority list, as language on GET /v1/transcripts/{video_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/arcmira/types/video_merge_list_response.py b/src/arcmira/types/video_merge_list_response.py new file mode 100644 index 0000000..14b092f --- /dev/null +++ b/src/arcmira/types/video_merge_list_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 .video_merge_list_response_merges_item import VideoMergeListResponseMergesItem + + +class VideoMergeListResponse(UniversalBaseModel): + merges: typing.List[VideoMergeListResponseMergesItem] = pydantic.Field() + """ + Your pending merges for the video, newest first. + """ + + 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/arcmira/types/video_merge_list_response_merges_item.py b/src/arcmira/types/video_merge_list_response_merges_item.py new file mode 100644 index 0000000..283b29a --- /dev/null +++ b/src/arcmira/types/video_merge_list_response_merges_item.py @@ -0,0 +1,84 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .video_merge_list_response_merges_item_status import VideoMergeListResponseMergesItemStatus + + +class VideoMergeListResponseMergesItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + Pending row id. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + source_name: typing_extensions.Annotated[ + str, + FieldMetadata(alias="sourceName"), + pydantic.Field(alias="sourceName", description="The name as it appears in the video."), + ] + """ + The name as it appears in the video. + """ + + target_entity_id: typing_extensions.Annotated[ + int, + FieldMetadata(alias="targetEntityId"), + pydantic.Field(alias="targetEntityId", description="Raw integer entity id the name refers to."), + ] + """ + Raw integer entity id the name refers to. + """ + + target_name: typing_extensions.Annotated[ + str, + FieldMetadata(alias="targetName"), + pydantic.Field(alias="targetName", description="Name of the target entity."), + ] + """ + Name of the target entity. + """ + + replace_with: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="replaceWith"), + pydantic.Field(alias="replaceWith", description="Respelling applied to the transcript text. Null when none."), + ] = None + """ + Respelling applied to the transcript text. Null when none. + """ + + status: VideoMergeListResponseMergesItemStatus = pydantic.Field() + """ + Review status. Always pending: only pending rows are listed. + """ + + created_at: typing_extensions.Annotated[ + str, + FieldMetadata(alias="createdAt"), + pydantic.Field(alias="createdAt", description="When the merge was submitted."), + ] + """ + When the merge was submitted. + """ + + 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/arcmira/types/video_merge_list_response_merges_item_status.py b/src/arcmira/types/video_merge_list_response_merges_item_status.py new file mode 100644 index 0000000..0107ea0 --- /dev/null +++ b/src/arcmira/types/video_merge_list_response_merges_item_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +VideoMergeListResponseMergesItemStatus = typing.Union[ + typing.Literal["pending", "approved", "rejected", "reverted"], typing.Any +] diff --git a/src/arcmira/types/video_merge_submitted_response.py b/src/arcmira/types/video_merge_submitted_response.py new file mode 100644 index 0000000..809ebd1 --- /dev/null +++ b/src/arcmira/types/video_merge_submitted_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 .video_merge_submitted_response_merge import VideoMergeSubmittedResponseMerge + + +class VideoMergeSubmittedResponse(UniversalBaseModel): + merge: VideoMergeSubmittedResponseMerge = pydantic.Field() + """ + The stored pending merge. + """ + + 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/arcmira/types/video_merge_submitted_response_merge.py b/src/arcmira/types/video_merge_submitted_response_merge.py new file mode 100644 index 0000000..938dcb6 --- /dev/null +++ b/src/arcmira/types/video_merge_submitted_response_merge.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .video_merge_submitted_response_merge_status import VideoMergeSubmittedResponseMergeStatus + + +class VideoMergeSubmittedResponseMerge(UniversalBaseModel): + """ + The stored pending merge. + """ + + id: int = pydantic.Field() + """ + Pending row id. Withdraw it with DELETE /v1/transcripts/{video_id}/merges/{id}. + """ + + video_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="videoId"), + pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), + ] + """ + YouTube video id (11 characters). + """ + + source_name: typing_extensions.Annotated[ + str, + FieldMetadata(alias="sourceName"), + pydantic.Field(alias="sourceName", description="The name as it appears in the video."), + ] + """ + The name as it appears in the video. + """ + + target_entity_id: typing_extensions.Annotated[ + int, + FieldMetadata(alias="targetEntityId"), + pydantic.Field(alias="targetEntityId", description="Raw integer entity id the name refers to."), + ] + """ + Raw integer entity id the name refers to. + """ + + replace_with: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="replaceWith"), + pydantic.Field(alias="replaceWith", description="Respelling applied to the transcript text. Null when none."), + ] = None + """ + Respelling applied to the transcript text. Null when none. + """ + + status: VideoMergeSubmittedResponseMergeStatus = pydantic.Field() + """ + Review status. Always pending on submit. + """ + + 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/arcmira/types/video_merge_submitted_response_merge_status.py b/src/arcmira/types/video_merge_submitted_response_merge_status.py new file mode 100644 index 0000000..700bab9 --- /dev/null +++ b/src/arcmira/types/video_merge_submitted_response_merge_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +VideoMergeSubmittedResponseMergeStatus = typing.Union[ + typing.Literal["pending", "approved", "rejected", "reverted"], typing.Any +] diff --git a/src/arcmira/types/webhook_secret_rotate_response.py b/src/arcmira/types/webhook_secret_rotate_response.py new file mode 100644 index 0000000..d671351 --- /dev/null +++ b/src/arcmira/types/webhook_secret_rotate_response.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class WebhookSecretRotateResponse(UniversalBaseModel): + webhook_secret: typing_extensions.Annotated[ + str, + FieldMetadata(alias="webhookSecret"), + pydantic.Field( + alias="webhookSecret", + description='The NEW webhook signing secret ("whsec_..."). Store it securely. The same Idempotency-Key can recover it for up to 24 hours while it remains the current or valid previous secret. A displaced or expired secret returns idempotency_result_expired without rotating again.', + ), + ] + """ + The NEW webhook signing secret ("whsec_..."). Store it securely. The same Idempotency-Key can recover it for up to 24 hours while it remains the current or valid previous secret. A displaced or expired secret returns idempotency_result_expired without rotating again. + """ + + webhook_secret_hint: typing_extensions.Annotated[ + str, + FieldMetadata(alias="webhookSecretHint"), + pydantic.Field( + alias="webhookSecretHint", + description="Last 4 characters of the new secret, for identifying which secret you hold.", + ), + ] + """ + Last 4 characters of the new secret, for identifying which secret you hold. + """ + + previous_secret_expires_at: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="previousSecretExpiresAt"), + pydantic.Field( + alias="previousSecretExpiresAt", + description="End of the 24-hour overlap window. Until then, deliveries carry an additional X-Arcmira-Signature-Previous header computed with the previous secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. Null when the monitor had no previous secret (nothing to overlap).", + ), + ] = None + """ + End of the 24-hour overlap window. Until then, deliveries carry an additional X-Arcmira-Signature-Previous header computed with the previous secret over the same {timestamp}.{payload} string, so you can verify with either secret while you roll. Null when the monitor had no previous secret (nothing to overlap). + """ + + 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/arcmira/types/withdrawn_response.py b/src/arcmira/types/withdrawn_response.py new file mode 100644 index 0000000..8d4d75a --- /dev/null +++ b/src/arcmira/types/withdrawn_response.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 WithdrawnResponse(UniversalBaseModel): + ok: bool = pydantic.Field() + """ + Always true: the pending row was withdrawn. + """ + + 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/arcmira/types/wrong_classification_change.py b/src/arcmira/types/wrong_classification_change.py new file mode 100644 index 0000000..13a80cc --- /dev/null +++ b/src/arcmira/types/wrong_classification_change.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 +from .wrong_classification_change_mention_class import WrongClassificationChangeMentionClass + + +class WrongClassificationChange(UniversalBaseModel): + """ + For issue_type wrong_classification: the commercial class the row should carry. + """ + + mention_class: typing.Optional[WrongClassificationChangeMentionClass] = pydantic.Field(default=None) + """ + The correct commercial classification. Values: ad_read (an ad-style promotion heard at that moment: a paid sponsor read, promo code, affiliate plug, thanks for supplied goods or venue, or a show promoting its own product as an ad), endorsement (an unpaid personal recommendation), mention (a neutral commercial mention). + """ + + 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/arcmira/types/wrong_classification_change_mention_class.py b/src/arcmira/types/wrong_classification_change_mention_class.py new file mode 100644 index 0000000..998735f --- /dev/null +++ b/src/arcmira/types/wrong_classification_change_mention_class.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +WrongClassificationChangeMentionClass = typing.Union[typing.Literal["ad_read", "endorsement", "mention"], typing.Any] diff --git a/src/arcmira/types/wrong_entity_change.py b/src/arcmira/types/wrong_entity_change.py new file mode 100644 index 0000000..b403b78 --- /dev/null +++ b/src/arcmira/types/wrong_entity_change.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 WrongEntityChange(UniversalBaseModel): + """ + For issue_type wrong_entity (and wrong_person): the entity the row should have been attributed to. + """ + + entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Public id ("ent_{n}") of the entity the row should point at. + """ + + entity_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Name of the correct entity when you do not have its id. + """ + + entity_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Type of the correct entity: person, organization, product, topic, or channel. + """ + + 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/arcmira/types/wrong_entity_type_change.py b/src/arcmira/types/wrong_entity_type_change.py new file mode 100644 index 0000000..9e8b7a9 --- /dev/null +++ b/src/arcmira/types/wrong_entity_type_change.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 +from .wrong_entity_type_change_field import WrongEntityTypeChangeField + + +class WrongEntityTypeChange(UniversalBaseModel): + """ + For issue_type wrong_entity_type: { "field": "type", "value": "organization" }. + """ + + field: typing.Optional[WrongEntityTypeChangeField] = pydantic.Field(default=None) + """ + Always "type". + """ + + value: typing.Optional[str] = pydantic.Field(default=None) + """ + The correct entity type: person, organization, product, topic, or channel. + """ + + 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/arcmira/types/wrong_entity_type_change_field.py b/src/arcmira/types/wrong_entity_type_change_field.py new file mode 100644 index 0000000..d0faa7e --- /dev/null +++ b/src/arcmira/types/wrong_entity_type_change_field.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +WrongEntityTypeChangeField = typing.Union[typing.Literal["type"], typing.Any] diff --git a/tests/fixtures/transcription-responses.json b/tests/fixtures/transcription-responses.json new file mode 100644 index 0000000..4fdfae1 --- /dev/null +++ b/tests/fixtures/transcription-responses.json @@ -0,0 +1,263 @@ +{ + "get_transcript": { + "status": 200, + "body": { + "state": "ready", + "video": { + "id": "dQw4w9WgXcQ", + "title": "TBPN | Tuesday, August 4", + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "published_at": "2026-08-04T17:00:00.000Z", + "duration_seconds": 3600, + "watch_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + "quality": "captions", + "source": "creator_captions", + "language": "en", + "languages": [ + { + "code": "en", + "name": "English", + "generated": false + }, + { + "code": "de", + "name": "Deutsch (auto)", + "generated": true + } + ], + "lines": [ + { + "start": 0, + "end": 20, + "text": "Ramp has been on the show for a while now." + }, + { + "start": 20, + "end": 45, + "text": "The pitch is still the same, spend less time on expenses." + }, + { + "start": 45, + "end": 70, + "text": "We asked the founders how they think about that." + }, + { + "start": 700, + "end": 730, + "text": "Back after the break with the rest of the interview." + } + ], + "rows_billed": 4, + "as_of": "2026-09-01T09:00:00.000Z", + "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got." + }, + "responses": { + "200": { + "status": 200, + "body": { + "state": "ready", + "video": { + "id": "dQw4w9WgXcQ", + "title": "TBPN | Tuesday, August 4", + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "published_at": "2026-08-04T17:00:00.000Z", + "duration_seconds": 3600, + "watch_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + "quality": "captions", + "source": "creator_captions", + "language": "en", + "languages": [ + { + "code": "en", + "name": "English", + "generated": false + }, + { + "code": "de", + "name": "Deutsch (auto)", + "generated": true + } + ], + "lines": [ + { + "start": 0, + "end": 20, + "text": "Ramp has been on the show for a while now." + }, + { + "start": 20, + "end": 45, + "text": "The pitch is still the same, spend less time on expenses." + }, + { + "start": 45, + "end": 70, + "text": "We asked the founders how they think about that." + }, + { + "start": 700, + "end": 730, + "text": "Back after the break with the rest of the interview." + } + ], + "rows_billed": 4, + "as_of": "2026-09-01T09:00:00.000Z", + "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got." + } + }, + "202": { + "status": 202, + "body": { + "state": "pending", + "quality": "premium", + "premium_job": { + "job_id": "00000000-0000-4000-8000-000000000001", + "status": "queued", + "next_poll_seconds": 30, + "eta_seconds": null + }, + "status_url": "/v1/transcriptions/00000000-0000-4000-8000-000000000001", + "next_poll_seconds": 30 + } + } + } + }, + "quote_transcription": { + "status": 200, + "body": { + "video_id": "dQw4w9WgXcQ", + "duration_seconds": 600, + "billing_scope": "full_video", + "owned": false, + "eligible": false, + "quote": { + "quarters": 1, + "rows": 75 + }, + "charge": { + "unit": "rows", + "amount": 75 + }, + "credits_per_row": 4, + "max_on_demand_cents": 0, + "on_demand_cents_per_unit": 0.4, + "prepare_url": "/v1/transcriptions", + "refund_policy": "Failed generation is refunded. Delivered unlocks are permanent." + }, + "responses": { + "200": { + "status": 200, + "body": { + "video_id": "dQw4w9WgXcQ", + "duration_seconds": 600, + "billing_scope": "full_video", + "owned": false, + "eligible": false, + "quote": { + "quarters": 1, + "rows": 75 + }, + "charge": { + "unit": "rows", + "amount": 75 + }, + "credits_per_row": 4, + "max_on_demand_cents": 0, + "on_demand_cents_per_unit": 0.4, + "prepare_url": "/v1/transcriptions", + "refund_policy": "Failed generation is refunded. Delivered unlocks are permanent." + } + } + } + }, + "get_transcription": { + "status": 200, + "body": { + "id": "00000000-0000-4000-8000-000000000001", + "videoId": "dQw4w9WgXcQ", + "status": "queued", + "state": "pending", + "stage": "queued", + "quote": { + "quarters": 1, + "rows": 75 + }, + "etaSeconds": 172, + "nextPollSeconds": 29, + "createdAt": "2026-10-01 00:00:00" + }, + "responses": { + "200": { + "status": 200, + "body": { + "id": "00000000-0000-4000-8000-000000000001", + "videoId": "dQw4w9WgXcQ", + "status": "queued", + "state": "pending", + "stage": "queued", + "quote": { + "quarters": 1, + "rows": 75 + }, + "etaSeconds": 172, + "nextPollSeconds": 29, + "createdAt": "2026-10-01 00:00:00" + } + } + } + }, + "list_transcriptions": { + "status": 200, + "body": { + "requests": [ + { + "id": "00000000-0000-4000-8000-000000000001", + "videoId": "dQw4w9WgXcQ", + "status": "queued", + "state": "pending", + "stage": "queued", + "quote": { + "quarters": 1, + "rows": 75 + }, + "etaSeconds": 172, + "nextPollSeconds": 29, + "createdAt": "2026-10-01 00:00:00", + "title": "First video" + } + ], + "has_more": false, + "next_cursor": null + }, + "responses": { + "200": { + "status": 200, + "body": { + "requests": [ + { + "id": "00000000-0000-4000-8000-000000000001", + "videoId": "dQw4w9WgXcQ", + "status": "queued", + "state": "pending", + "stage": "queued", + "quote": { + "quarters": 1, + "rows": 75 + }, + "etaSeconds": 172, + "nextPollSeconds": 29, + "createdAt": "2026-10-01 00:00:00", + "title": "First video" + } + ], + "has_more": false, + "next_cursor": null + } + } + } + } +} diff --git a/tests/test_generation.py b/tests/test_generation.py new file mode 100644 index 0000000..231af0a --- /dev/null +++ b/tests/test_generation.py @@ -0,0 +1,30 @@ +import copy +import json +import runpy +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +TOOLS = runpy.run_path(str(ROOT / 'scripts/prepare-openapi.py')) + +class GenerationTests(unittest.TestCase): + def test_unknown_and_ambiguous_cursor_collections_fail(self): + for props in ({'mystery': {'type':'array'}}, {'requests':{'type':'array'}, 'episodes':{'type':'array'}}): + with self.assertRaisesRegex(ValueError, 'Unknown or ambiguous'): + TOOLS['collection']({}, {'properties': props}) + + def test_actual_contract_generates_union_key_and_collections(self): + doc = json.loads((ROOT / 'fern/openapi.json').read_text()) + before = copy.deepcopy(doc) + prepared = TOOLS['prepare'](doc, json.loads((ROOT / 'fern/method-names.json').read_text())) + self.assertEqual(doc, before) + union = prepared['components']['schemas']['TranscriptResult'] + self.assertEqual(union['discriminator']['propertyName'], 'state') + self.assertEqual(set(union['discriminator']['mapping']), {'ready','pending'}) + for path, collection in [('/v1/transcriptions','requests'),('/v1/channels/{channel_id}/videos','episodes')]: + self.assertEqual(prepared['paths'][path]['get']['x-fern-pagination']['results'], '$response.'+collection) + post = prepared['paths']['/v1/transcriptions']['post'] + self.assertTrue(next(p for p in post['parameters'] if p['name']=='Idempotency-Key')['required']) + self.assertIn('max_rows', post['requestBody']['content']['application/json']['schema']['required']) + +if __name__ == '__main__': unittest.main() diff --git a/tests/test_transcription.py b/tests/test_transcription.py new file mode 100644 index 0000000..816f660 --- /dev/null +++ b/tests/test_transcription.py @@ -0,0 +1,138 @@ +import asyncio +import json +import threading +import unittest +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from urllib.parse import parse_qs, urlparse + +from arcmira import Arcmira, AsyncArcmira +from arcmira.core.api_error import ApiError +from arcmira.types.transcript_result import TranscriptResult_Ready, TranscriptResult_Pending + +FIXTURES = json.loads((Path(__file__).parent / 'fixtures/transcription-responses.json').read_text()) +QUOTE = FIXTURES['quote_transcription']['body'] +REQUEST = FIXTURES['get_transcription']['body'] +def error(code, kind): + return dict(type=kind, code=code, message=code, doc_url='https://arcmira.com/docs/errors', request_id='fixture-request') + +CURSOR = 'signed+/opaque==&cursor' +PENDING = dict(state='pending', quality='premium', premium_job=dict(job_id=REQUEST['id'], status='queued', next_poll_seconds=5), status_url='/v1/transcriptions/'+REQUEST['id'], next_poll_seconds=5) +CALLS = [] +RECEIPTS = {} + +class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def do_GET(self): + self.answer() + + def do_POST(self): + self.answer() + + def answer(self): + url = urlparse(self.path) + query = parse_qs(url.query) + body = self.rfile.read(int(self.headers.get('Content-Length', 0))).decode() + CALLS.append((url.path, query, dict(self.headers), body)) + status, extra = 200, {} + if url.path.endswith('/quote'): + result = QUOTE + elif url.path == '/v1/transcriptions' and self.command == 'POST': + key = self.headers['Idempotency-Key'] + replay = key in RECEIPTS + if replay and RECEIPTS[key] != body: + status, result = 409, {'error': error('idempotency_conflict', 'conflict_error')} + else: + RECEIPTS[key] = body + status = 200 if replay else 202 + result = {'request': REQUEST, **({'existing': True} if replay else {})} + if replay: extra['Idempotency-Replayed'] = 'true' + elif url.path == '/v1/transcriptions': + result = {'requests': [{**REQUEST, 'id': 'request-2' if 'cursor' in query else 'request-1'}], 'has_more': 'cursor' not in query, 'next_cursor': None if 'cursor' in query else CURSOR} + elif '/channels/' in url.path: + episode = dict(video_id='video-2' if 'cursor' in query else 'video-1', channel_id='UC-test', watch_url='https://arcmira.com/video/test') + result = dict(channel={'youtube_channel_id':'UC-test','name':'Fixture'}, episodes=[episode], returned=1, has_more='cursor' not in query, next_cursor=None if 'cursor' in query else CURSOR, indexed_through=None, index_age_days=None, as_of=None, note='Fixture') + elif url.path.endswith('/pending0000'): + status, result = 202, PENDING + elif url.path.endswith('/refused0000'): + status, result = 403, {'error': error('purchase_required', 'permission_error'), 'quote': QUOTE, 'prepare_url': '/v1/transcriptions'} + else: + result = FIXTURES['get_transcript']['body'] + self.send_response(status) + for name, value in {'Content-Type': 'application/json', 'Retry-After': '5', **extra}.items(): self.send_header(name, value) + self.end_headers() + self.wfile.write(json.dumps(result).encode()) + +class GeneratedClientTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) + cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True) + cls.thread.start() + cls.base = f'http://127.0.0.1:{cls.server.server_port}' + cls.client = Arcmira(api_key='local-test-key', base_url=cls.base, max_retries=0) + + @classmethod + def tearDownClass(cls): + cls.server.shutdown() + cls.server.server_close() + cls.thread.join() + + def test_ready_pending_status_and_union(self): + ready = self.client.transcripts.with_raw_response.get(video_id='dQw4w9WgXcQ') + self.assertIsInstance(ready.data, TranscriptResult_Ready) + self.assertEqual(ready.status_code, 200) + self.assertTrue(ready.data.lines) + pending = self.client.transcripts.with_raw_response.get(video_id='pending0000', quality='premium') + self.assertIsInstance(pending.data, TranscriptResult_Pending) + self.assertEqual(pending.status_code, 202) + self.assertEqual(pending.data.status_url, PENDING['status_url']) + self.assertEqual(pending.headers['retry-after'], '5') + + def test_quote_and_refusal(self): + quote = self.client.transcripts.quote(video_id='dQw4w9WgXcQ') + self.assertEqual(quote.quote.rows, QUOTE['quote']['rows']) + with self.assertRaises(ApiError) as caught: + self.client.transcripts.get(video_id='refused0000', quality='premium') + self.assertEqual(caught.exception.status_code, 403) + self.assertEqual(caught.exception.body.quote, QUOTE) + + def test_preparation_exact_intent_replay_and_required_key(self): + intent = dict(video_id='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0, idempotency_key='python-saved-intent') + first = self.client.transcripts.with_raw_response.request(**intent) + replay = self.client.transcripts.with_raw_response.request(**intent) + self.assertEqual(first.status_code, 202) + self.assertEqual(replay.status_code, 200) + self.assertEqual(replay.data.request.id, first.data.request.id) + self.assertTrue(replay.data.existing) + self.assertEqual(replay.headers['idempotency-replayed'], 'true') + self.assertEqual(json.loads(CALLS[-1][3]), dict(videoId='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0)) + with self.assertRaises(ApiError) as caught: + self.client.transcripts.request(**{**intent, 'max_rows':600}) + self.assertEqual(caught.exception.status_code, 409) + with self.assertRaises(TypeError): + self.client.transcripts.request(video_id='dQw4w9WgXcQ', max_rows=300) + + def test_request_and_episode_arrays_preserve_opaque_cursor(self): + before = len(CALLS) + self.assertEqual([x.id for x in self.client.transcripts.list_requests(limit=1)], ['request-1','request-2']) + self.assertEqual([x.video_id for x in self.client.channels.videos.list(channel_id='UC-test', limit=1)], ['video-1','video-2']) + continued = [call for call in CALLS[before:] if 'cursor' in call[1]] + self.assertEqual(len(continued), 2) + for call in continued: + self.assertEqual(call[1]['cursor'], [CURSOR]) + self.assertEqual(call[1]['limit'], ['1']) + + def test_async_pending_and_pagination(self): + async def run(): + client = AsyncArcmira(api_key='local-test-key', base_url=self.base, max_retries=0) + pending = await client.transcripts.with_raw_response.get(video_id='pending0000', quality='premium') + self.assertEqual(pending.status_code, 202) + self.assertIsInstance(pending.data, TranscriptResult_Pending) + rows = [row.id async for row in await client.transcripts.list_requests(limit=1)] + self.assertEqual(rows, ['request-1','request-2']) + asyncio.run(run()) + +if __name__ == '__main__': unittest.main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..b9a90fd --- /dev/null +++ b/uv.lock @@ -0,0 +1,327 @@ +version = 1 +revision = 1 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427 }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "idna", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592 }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079 }, +] + +[[package]] +name = "arcmira" +source = { editable = "." } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.21.2,<1" }, + { name = "pydantic", specifier = ">=1.9.2,<3" }, + { name = "typing-extensions", specifier = ">=4.0.0" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", version = "4.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "anyio", version = "4.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583 }, +] + +[[package]] +name = "pydantic" +version = "2.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "annotated-types", version = "0.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection", version = "0.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "typing-inspection", version = "0.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589 }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/6b/8f79692844269427abb3e4dd9e68edfcbe65ae25527d99183214de716c59/pydantic_core-2.46.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6", size = 2076533 }, + { url = "https://files.pythonhosted.org/packages/bd/d0/c787604c71c2bdcda1a5656942fc822cd0f9cd879b9484bb84fc42172703/pydantic_core-2.46.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615", size = 1924650 }, + { url = "https://files.pythonhosted.org/packages/4a/77/ca2f8e997d9bfdb32205297aff38f210f398822d895b1af1b59fd9df9c13/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb", size = 1951261 }, + { url = "https://files.pythonhosted.org/packages/a0/53/bd12e1a9255df4edee00353778e2614b5346265d51e1567ab72153e803a2/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b", size = 2021808 }, + { url = "https://files.pythonhosted.org/packages/d7/41/f7f312751ebc6d6767da91964a9c7954c18e226a1720ab234e3dfb9d6c17/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6", size = 2196184 }, + { url = "https://files.pythonhosted.org/packages/3d/93/ce93aa030ab6bac4683ba8861e7baad89dd24b02e66b8801a0e4f6a00311/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793", size = 2238212 }, + { url = "https://files.pythonhosted.org/packages/34/a1/c8e6b66f499f510752c07a092dfe27621f9c255635e59d38704b5681c35a/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b", size = 2064073 }, + { url = "https://files.pythonhosted.org/packages/5c/fa/605e2b127ee30dbf4b1da9da4843587cf2b2d16486c241cc7a5be2d2c1bd/pydantic_core-2.46.5-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461", size = 2093102 }, + { url = "https://files.pythonhosted.org/packages/4a/f7/1ab28093c09032ddce7c92c7a55d503b6ecd70f42c32492946c1cb5477b1/pydantic_core-2.46.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736", size = 2133452 }, + { url = "https://files.pythonhosted.org/packages/30/c8/47c79b756f12f85e8b0fbdb2b495f6b6eb32e6c98a4beae7a570a0b7c63c/pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3", size = 2146477 }, + { url = "https://files.pythonhosted.org/packages/13/5c/79fc00cb8f651d6061991de8d7cedf1c78c73cbd4862c42ef418f03b8bfa/pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f", size = 2300832 }, + { url = "https://files.pythonhosted.org/packages/b4/72/dd1a29853cf6d22a1ebd9e3baf0239cbc57d2d16caff36a89e38eb9b1db3/pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1", size = 2320505 }, + { url = "https://files.pythonhosted.org/packages/ec/d1/ba4a8e06a9ddad0b4caf69cfaeecc0fbfcec20473bd808f5127fd16491c4/pydantic_core-2.46.5-cp310-cp310-win32.whl", hash = "sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069", size = 1956853 }, + { url = "https://files.pythonhosted.org/packages/f2/94/205ed9d7ddaf44acd489889708ea124a3f41bdb42c141c8684d528ad0e7a/pydantic_core-2.46.5-cp310-cp310-win_amd64.whl", hash = "sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d", size = 2042551 }, + { url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737 }, + { url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751 }, + { url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231 }, + { url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708 }, + { url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914 }, + { url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622 }, + { url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091 }, + { url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904 }, + { url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244 }, + { url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901 }, + { url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425 }, + { url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566 }, + { url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258 }, + { url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030 }, + { url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234 }, + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516 }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874 }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772 }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832 }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645 }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935 }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284 }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889 }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006 }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408 }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609 }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618 }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475 }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140 }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729 }, + { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885 }, + { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768 }, + { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241 }, + { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975 }, + { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542 }, + { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692 }, + { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633 }, + { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235 }, + { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367 }, + { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420 }, + { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588 }, + { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866 }, + { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580 }, + { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980 }, + { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213 }, + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081 }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497 }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130 }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371 }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822 }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756 }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352 }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777 }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312 }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067 }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516 }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223 }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827 }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648 }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652 }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829 }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716 }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216 }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635 }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369 }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238 }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740 }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425 }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306 }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589 }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882 }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210 }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180 }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515 }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276 }, + { url = "https://files.pythonhosted.org/packages/96/cc/4c88abc035cc0d8b2646a715d8c4145fad7d95817eb5f18297066b21e20e/pydantic_core-2.46.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c583b927a8838dab890706a6fa7573fbb8b70e24000ef9f7238e2d6f6435a5ed", size = 2078970 }, + { url = "https://files.pythonhosted.org/packages/b4/59/fa3ef009cc1b2ca3753fd6869ee461b0b5b67c420cf659e32a12be027a6a/pydantic_core-2.46.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdc8b74ecc48c0cb1e9607a05ec4e9e88db60a19ffcc9a1d5f9088ede40c8dc0", size = 1917185 }, + { url = "https://files.pythonhosted.org/packages/15/5e/b3d8901f9775ad928077c3155c36f56fc1c813285e3986ed736a5fbf538e/pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b10e3e8fd7ddc2bd915848a2768e44c15b22936f1cc54c462ad1164deb02655", size = 1955266 }, + { url = "https://files.pythonhosted.org/packages/aa/9e/5522b09d12e8720013f2e4ac174999f40a05501bd15ad2bfa197bc136198/pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f077d0b97ab11fa7dcc633fca53515f290bca8a8a633e966d5b6d1879d9ed01a", size = 2023466 }, + { url = "https://files.pythonhosted.org/packages/89/2a/a5267bf2c6c7ded3f282b315e5f0cf2c58008c15b917a652fc32f92d6775/pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b0fc826b16c55e561e5d2a0c5c77b051ba1d92808118c4e4b5390f5e0cf191d", size = 2198448 }, + { url = "https://files.pythonhosted.org/packages/e9/a5/f72c192aba23924065946728e4fba96f73939b90e5aa4f7d41e728aea8d4/pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef3fbbf161dc9351a2fe0422e51b129f9e97e42385bd0320b309c15f7d287dd8", size = 2240121 }, + { url = "https://files.pythonhosted.org/packages/a8/9e/0c0cc24149429c030bef1a5c1776150e7e61fcbbfc068c6d1f9de90eb259/pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:978e7b97d4824b5be09c69fb70507cbde3b0323fc147332ca40a94d9a6a0ebbf", size = 2067262 }, + { url = "https://files.pythonhosted.org/packages/1c/00/a2b8690a11d909d9ec9c4eb4d084b4d2e1b227e9b2e74f5926cd39096245/pydantic_core-2.46.5-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:9b68938dd5b0c783d88ff8e2dcc69451b5eb936fe212d516b21b9d5567f6d464", size = 2095116 }, + { url = "https://files.pythonhosted.org/packages/bf/7e/d3088a2717b7bb316d8d0e64a4b0caf994769e88c56df79df547d75c1dc0/pydantic_core-2.46.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:771cf63ae0b1b50dd22e5f3e3549fab5f3f4ff1635d352a9e1a97fe01c7b2e64", size = 2134727 }, + { url = "https://files.pythonhosted.org/packages/fa/a4/55a9e0ef61cfd1cbf4289059eb68a3eab765fca8ead6f9991d7de027d42e/pydantic_core-2.46.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c6be839a5a8312626b32029a415644a0846b420bc8b52b95b28cd92da162168", size = 2147932 }, + { url = "https://files.pythonhosted.org/packages/2d/25/d2fbc9d59f91f6c50c0d2ec032041c5e3295d68325ade06ec93fa82da43c/pydantic_core-2.46.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:895395f8918627b04efb1ad2a4cf605387143300ba03304cd1dfa6d03f5e095e", size = 2301528 }, + { url = "https://files.pythonhosted.org/packages/9d/76/eccc0528d1421e298b42f85650cf021f0f7c42f502c7e58808db4a672bdb/pydantic_core-2.46.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fc8515076c11f3cfdf4fb142dcca0fe384b1230a3b5415458ac84f3e0903ec13", size = 2322431 }, + { url = "https://files.pythonhosted.org/packages/6f/0c/ffab5a9a0fb82825c44f00dea8ec9d540d2e1e4ab2f1c4f0c32bb8b37fd9/pydantic_core-2.46.5-cp39-cp39-win32.whl", hash = "sha256:3d2652072b2d774947ba5cf78a9e59644ac62ee572daf6dd2e1dfe905e15b2b7", size = 1958681 }, + { url = "https://files.pythonhosted.org/packages/86/89/8bb47660fed8c16adf1aae301ba149442e8fd220c126bbea2d24b987abb8/pydantic_core-2.46.5-cp39-cp39-win_amd64.whl", hash = "sha256:3aa166e99c4f2985407fb8714aebede877ecb5455cf321b606adca926d30d5a0", size = 2046649 }, + { url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333 }, + { url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713 }, + { url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926 }, + { url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560 }, + { url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531 }, + { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686 }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750 }, +] From c69cda745785d77a349e0ee8ca3e99c821706d5d Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 21:29:44 -0700 Subject: [PATCH 02/12] Keep generated client names and exclusions consistent --- reference.md | 428 +----------- scripts/prepare-openapi.py | 21 + src/arcmira/__init__.py | 118 +--- src/arcmira/channels/__init__.py | 10 +- src/arcmira/channels/client.py | 23 +- src/arcmira/channels/raw_client.py | 25 +- src/arcmira/channels/sponsors/__init__.py | 6 +- src/arcmira/channels/sponsors/client.py | 13 +- src/arcmira/channels/sponsors/raw_client.py | 11 - .../channels/sponsors/types/__init__.py | 8 +- .../types/list_sponsors_request_src.py | 5 - src/arcmira/channels/types/__init__.py | 34 - .../types/coverage_channels_request_src.py | 5 - src/arcmira/channels/videos/__init__.py | 31 - src/arcmira/channels/videos/client.py | 11 - src/arcmira/channels/videos/raw_client.py | 13 - src/arcmira/channels/videos/types/__init__.py | 34 - .../videos/types/list_videos_request_src.py | 5 - src/arcmira/client.py | 135 ---- src/arcmira/entities/__init__.py | 23 +- src/arcmira/entities/client.py | 49 +- src/arcmira/entities/mentions/__init__.py | 5 +- src/arcmira/entities/mentions/client.py | 11 - src/arcmira/entities/mentions/raw_client.py | 13 - .../entities/mentions/types/__init__.py | 4 +- .../types/list_mentions_request_src.py | 5 - src/arcmira/entities/raw_client.py | 47 +- .../entities/recommendations/__init__.py | 9 +- .../entities/recommendations/client.py | 11 - .../entities/recommendations/raw_client.py | 13 - .../recommendations/types/__init__.py | 6 +- .../types/list_recommendations_request_src.py | 5 - src/arcmira/entities/types/__init__.py | 15 +- .../types/momentum_entities_request_src.py | 5 - .../types/resolve_entities_request_src.py | 5 - .../types/search_entities_request_src.py | 5 - src/arcmira/mentions/__init__.py | 6 - src/arcmira/mentions/client.py | 22 - src/arcmira/mentions/raw_client.py | 24 - src/arcmira/mentions/types/__init__.py | 6 - .../types/count_mentions_request_src.py | 5 - .../types/list_mentions_request_src.py | 5 - src/arcmira/meta/__init__.py | 3 - src/arcmira/meta/client.py | 263 -------- src/arcmira/meta/raw_client.py | 612 ------------------ src/arcmira/raw_client.py | 294 --------- src/arcmira/recommendations/__init__.py | 13 +- src/arcmira/recommendations/client.py | 11 - src/arcmira/recommendations/raw_client.py | 13 - src/arcmira/recommendations/types/__init__.py | 8 +- .../types/list_recommendations_request_src.py | 5 - src/arcmira/transcripts/__init__.py | 28 +- src/arcmira/transcripts/client.py | 113 +--- src/arcmira/transcripts/raw_client.py | 141 +--- src/arcmira/transcripts/types/__init__.py | 20 +- .../types/captions_transcripts_request_src.py | 5 - .../types/get_transcripts_request_src.py | 5 - .../list_requests_transcripts_request_src.py | 5 - .../types/search_transcripts_request_src.py | 5 - .../types/status_transcripts_request_src.py | 5 - src/arcmira/types/__init__.py | 63 +- src/arcmira/types/search_request_type.py | 5 - ...ption_request.py => transcript_request.py} | 22 +- ...charge.py => transcript_request_charge.py} | 6 +- .../types/transcript_request_charge_unit.py | 5 + ...py => transcript_request_list_response.py} | 6 +- ...pt_request_list_response_requests_item.py} | 4 +- ...t_quote.py => transcript_request_quote.py} | 2 +- src/arcmira/types/transcript_request_stage.py | 5 + src/arcmira/types/transcript_request_state.py | 5 + ...status.py => transcript_request_status.py} | 2 +- ... => transcript_request_submit_response.py} | 6 +- .../transcription_request_charge_unit.py | 5 - .../types/transcription_request_stage.py | 5 - .../types/transcription_request_state.py | 5 - 75 files changed, 222 insertions(+), 2708 deletions(-) delete mode 100644 src/arcmira/channels/sponsors/types/list_sponsors_request_src.py delete mode 100644 src/arcmira/channels/types/__init__.py delete mode 100644 src/arcmira/channels/types/coverage_channels_request_src.py delete mode 100644 src/arcmira/channels/videos/types/__init__.py delete mode 100644 src/arcmira/channels/videos/types/list_videos_request_src.py delete mode 100644 src/arcmira/entities/mentions/types/list_mentions_request_src.py delete mode 100644 src/arcmira/entities/recommendations/types/list_recommendations_request_src.py delete mode 100644 src/arcmira/entities/types/momentum_entities_request_src.py delete mode 100644 src/arcmira/entities/types/resolve_entities_request_src.py delete mode 100644 src/arcmira/entities/types/search_entities_request_src.py delete mode 100644 src/arcmira/mentions/types/count_mentions_request_src.py delete mode 100644 src/arcmira/mentions/types/list_mentions_request_src.py delete mode 100644 src/arcmira/meta/__init__.py delete mode 100644 src/arcmira/meta/client.py delete mode 100644 src/arcmira/meta/raw_client.py delete mode 100644 src/arcmira/raw_client.py delete mode 100644 src/arcmira/recommendations/types/list_recommendations_request_src.py delete mode 100644 src/arcmira/transcripts/types/captions_transcripts_request_src.py delete mode 100644 src/arcmira/transcripts/types/get_transcripts_request_src.py delete mode 100644 src/arcmira/transcripts/types/list_requests_transcripts_request_src.py delete mode 100644 src/arcmira/transcripts/types/search_transcripts_request_src.py delete mode 100644 src/arcmira/transcripts/types/status_transcripts_request_src.py delete mode 100644 src/arcmira/types/search_request_type.py rename src/arcmira/types/{transcription_request.py => transcript_request.py} (84%) rename src/arcmira/types/{transcription_request_charge.py => transcript_request_charge.py} (78%) create mode 100644 src/arcmira/types/transcript_request_charge_unit.py rename src/arcmira/types/{transcription_list_response.py => transcript_request_list_response.py} (77%) rename src/arcmira/types/{transcription_list_response_requests_item.py => transcript_request_list_response_requests_item.py} (82%) rename src/arcmira/types/{transcription_request_quote.py => transcript_request_quote.py} (93%) create mode 100644 src/arcmira/types/transcript_request_stage.py create mode 100644 src/arcmira/types/transcript_request_state.py rename src/arcmira/types/{transcription_request_status.py => transcript_request_status.py} (84%) rename src/arcmira/types/{transcription_submit_response.py => transcript_request_submit_response.py} (88%) delete mode 100644 src/arcmira/types/transcription_request_charge_unit.py delete mode 100644 src/arcmira/types/transcription_request_stage.py delete mode 100644 src/arcmira/types/transcription_request_state.py diff --git a/reference.md b/reference.md index 0ce559d..8aa0782 100644 --- a/reference.md +++ b/reference.md @@ -1,85 +1,4 @@ # Reference -
client.search(...) -> SearchResolveResponse -
-
- -#### 📝 Description - -
-
- -
-
- -Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from arcmira import Arcmira -from arcmira.environment import ArcmiraEnvironment - -client = Arcmira( - api_key="", - environment=ArcmiraEnvironment.DEFAULT, -) - -client.search( - q="q", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**q:** `str` — Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. - -
-
- -
-
- -**type:** `typing.Optional[SearchRequestType]` — Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- ## Health
client.health.check() -> HealthResponse
@@ -126,219 +45,6 @@ client.health.check()
- - -
- -## Meta -
client.meta.get_openapi_document() -> OpenApiDocument -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from arcmira import Arcmira -from arcmira.environment import ArcmiraEnvironment - -client = Arcmira( - api_key="", - environment=ArcmiraEnvironment.DEFAULT, -) - -client.meta.get_openapi_document() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.meta.create_signup(...) -> SignupSentResponse -
-
- -#### 📝 Description - -
-
- -
-
- -Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from arcmira import Arcmira -from arcmira.environment import ArcmiraEnvironment - -client = Arcmira( - api_key="", - environment=ArcmiraEnvironment.DEFAULT, -) - -client.meta.create_signup( - email="email", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**email:** `str` — The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. - -
-
- -
-
- -**src:** `typing.Optional[str]` — The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.meta.verify_signup(...) -> SignupVerifiedResponse -
-
- -#### 📝 Description - -
-
- -
-
- -Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from arcmira import Arcmira -from arcmira.environment import ArcmiraEnvironment - -client = Arcmira( - api_key="", - environment=ArcmiraEnvironment.DEFAULT, -) - -client.meta.verify_signup( - email="email", - code="code", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**email:** `str` — The address the code was sent to. - -
-
- -
-
- -**code:** `str` — The six digit code from the email. Ten minutes, five attempts, then a new send is required. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- -
@@ -567,14 +273,6 @@ client.entities.search(
-**src:** `typing.Optional[SearchEntitiesRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -672,14 +370,6 @@ client.entities.resolve(
-**src:** `typing.Optional[ResolveEntitiesRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -986,14 +676,6 @@ client.entities.momentum(
-**src:** `typing.Optional[MomentumEntitiesRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1162,14 +844,6 @@ client.mentions.list()
-**src:** `typing.Optional[ListMentionsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1297,14 +971,6 @@ client.mentions.count()
-**src:** `typing.Optional[CountMentionsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1465,14 +1131,6 @@ client.recommendations.list()
-**src:** `typing.Optional[ListRecommendationsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1849,14 +1507,6 @@ client.transcripts.search(
-**src:** `typing.Optional[SearchTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1978,14 +1628,6 @@ client.transcripts.get(
-**src:** `typing.Optional[GetTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2132,14 +1774,6 @@ client.transcripts.captions(
-**src:** `typing.Optional[CaptionsTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2152,7 +1786,7 @@ client.transcripts.captions(
-
client.transcripts.list_requests(...) -> TranscriptionListResponse +
client.transcripts.list_requests(...) -> TranscriptRequestListResponse
@@ -2227,14 +1861,6 @@ client.transcripts.list_requests()
-**src:** `typing.Optional[ListRequestsTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2247,7 +1873,7 @@ client.transcripts.list_requests()
-
client.transcripts.request(...) -> TranscriptionSubmitResponse +
client.transcripts.request(...) -> TranscriptRequestSubmitResponse
@@ -2353,7 +1979,7 @@ client.transcripts.request(
-
client.transcripts.status(...) -> TranscriptionRequest +
client.transcripts.status(...) -> TranscriptRequest
@@ -2414,14 +2040,6 @@ client.transcripts.status(
-**src:** `typing.Optional[StatusTranscriptsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2496,14 +2114,6 @@ client.channels.coverage(
-**src:** `typing.Optional[CoverageChannelsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -4377,14 +3987,6 @@ client.channels.sponsors.list(
-**src:** `typing.Optional[ListSponsorsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -4491,14 +4093,6 @@ client.channels.videos.list(
-**src:** `typing.Optional[ListVideosRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -5477,14 +5071,6 @@ client.entities.mentions.list(
-**src:** `typing.Optional[ListMentionsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -5631,14 +5217,6 @@ client.entities.recommendations.list(
-**src:** `typing.Optional[ListRecommendationsRequestSrc]` — The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - -
-
- -
-
- **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
diff --git a/scripts/prepare-openapi.py b/scripts/prepare-openapi.py index 0d89692..e029821 100644 --- a/scripts/prepare-openapi.py +++ b/scripts/prepare-openapi.py @@ -28,6 +28,26 @@ def collection(document, schema): def prepare(document, names): doc = copy.deepcopy(document) + for path in ('/v1/openapi.json', '/v1/signups', '/v1/signups/verify', '/v1/search'): + del doc['paths'][path] + type_names = { + 'TranscriptionRequest': 'TranscriptRequest', + 'TranscriptionSubmitResponse': 'TranscriptRequestSubmitResponse', + 'TranscriptionListResponse': 'TranscriptRequestListResponse', + } + schemas = doc['components']['schemas'] + for source, target in type_names.items(): + schemas[target] = schemas.pop(source) + def rename_refs(value): + if isinstance(value, dict): + ref = value.get('$ref', '') + source = ref.removeprefix('#/components/schemas/') + if source in type_names: + value['$ref'] = '#/components/schemas/' + type_names[source] + for child in value.values(): rename_refs(child) + elif isinstance(value, list): + for child in value: rename_refs(child) + rename_refs(doc) # Fern 5.131.1 loses inherited example fields in this object intersection. suggestion = doc['components']['schemas']['ResolveSuggestion'] members = [resolve(doc, part) for part in suggestion.pop('allOf')] @@ -47,6 +67,7 @@ def prepare(document, names): for method, op in methods.items(): if method not in {'get', 'post', 'put', 'patch', 'delete'}: continue + op['parameters'] = [p for p in op.get('parameters', []) if not (p.get('in') == 'query' and p['name'] == 'src')] key = method + ' ' + re.sub(r'\{[^}]+\}', '{}', path) if op.get('operationId') in special: group, name = special[op['operationId']] diff --git a/src/arcmira/__init__.py b/src/arcmira/__init__.py index 4030353..de0f351 100644 --- a/src/arcmira/__init__.py +++ b/src/arcmira/__init__.py @@ -297,7 +297,6 @@ ResolveSuggestion, ResolveSuggestionMatch, ResolveSuggestionReason, - SearchRequestType, SearchResolveResponse, SearchResolveResponseEntity, SignupSentResponse, @@ -350,6 +349,16 @@ TranscriptPurchaseQuoteCharge, TranscriptPurchaseQuoteChargeUnit, TranscriptQuote, + TranscriptRequest, + TranscriptRequestCharge, + TranscriptRequestChargeUnit, + TranscriptRequestListResponse, + TranscriptRequestListResponseRequestsItem, + TranscriptRequestQuote, + TranscriptRequestStage, + TranscriptRequestState, + TranscriptRequestStatus, + TranscriptRequestSubmitResponse, TranscriptResponse, TranscriptResponseAccess, TranscriptResponseAccessGate, @@ -381,16 +390,6 @@ TranscriptSettings, TranscriptSettingsQuality, TranscriptVideo, - TranscriptionListResponse, - TranscriptionListResponseRequestsItem, - TranscriptionRequest, - TranscriptionRequestCharge, - TranscriptionRequestChargeUnit, - TranscriptionRequestQuote, - TranscriptionRequestStage, - TranscriptionRequestState, - TranscriptionRequestStatus, - TranscriptionSubmitResponse, VideoCaptionsResponse, VideoMergeListResponse, VideoMergeListResponseMergesItem, @@ -427,7 +426,6 @@ health, me, mentions, - meta, monitors, organizations, people, @@ -439,17 +437,9 @@ transcripts, ) from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient - from .channels import CoverageChannelsRequestSrc from .client import Arcmira, AsyncArcmira from .corrections import SubmitCorrectionsRequestAnchor, SubmitCorrectionsRequestKind - from .entities import ( - LookupEntitiesRequestType, - MomentumEntitiesRequestSrc, - ResolveEntitiesRequestSrc, - ResolveEntitiesRequestType, - SearchEntitiesRequestSrc, - SearchEntitiesRequestType, - ) + from .entities import LookupEntitiesRequestType, ResolveEntitiesRequestType, SearchEntitiesRequestType from .environment import ArcmiraEnvironment from .feedback import ( SubmitFeedbackRequestCorrectionsItem, @@ -463,32 +453,18 @@ from .me import UpdateSettingsMeRequestTranscripts, UpdateSettingsMeRequestTranscriptsQuality from .mentions import ( CountMentionsRequestMode, - CountMentionsRequestSrc, ListMentionsRequestDetails, ListMentionsRequestEntityType, ListMentionsRequestSentiment, - ListMentionsRequestSrc, ) from .monitors import CreateMonitorsRequestNotifyFrequency, UpdateMonitorsRequestNotifyFrequency - from .recommendations import ( - ListRecommendationsRequestEntityType, - ListRecommendationsRequestMentionClass, - ListRecommendationsRequestSrc, - ) + from .recommendations import ListRecommendationsRequestEntityType, ListRecommendationsRequestMentionClass from .trackers import ( CreateTrackersRequestEntityType, CreateTrackersRequestPersonMatchMode, UpdateTrackersRequestPersonMatchMode, ) - from .transcripts import ( - CaptionsTranscriptsRequestSrc, - GetTranscriptsRequestQuality, - GetTranscriptsRequestSrc, - ListRequestsTranscriptsRequestSrc, - SearchTranscriptsRequestSource, - SearchTranscriptsRequestSrc, - StatusTranscriptsRequestSrc, - ) + from .transcripts import GetTranscriptsRequestQuality, SearchTranscriptsRequestSource _dynamic_imports: typing.Dict[str, str] = { "AccountSettings": ".types", "Alert": ".types", @@ -502,7 +478,6 @@ "BadRankingChange": ".types", "BadRequestError": ".errors", "CaptionTrack": ".types", - "CaptionsTranscriptsRequestSrc": ".transcripts", "ChannelCoverageResponse": ".types", "ChannelCoverageResponseChannel": ".types", "ChannelCoverageResponseChannelSourceMix": ".types", @@ -554,8 +529,6 @@ "CorrectionAcceptedResponseKind": ".types", "CorrectionSeqMismatchResponse": ".types", "CountMentionsRequestMode": ".mentions", - "CountMentionsRequestSrc": ".mentions", - "CoverageChannelsRequestSrc": ".channels", "CreateMonitorsRequestNotifyFrequency": ".monitors", "CreateTrackersRequestEntityType": ".trackers", "CreateTrackersRequestPersonMatchMode": ".trackers", @@ -669,7 +642,6 @@ "ForbiddenError": ".errors", "FreeformSuggestedChange": ".types", "GetTranscriptsRequestQuality": ".transcripts", - "GetTranscriptsRequestSrc": ".transcripts", "HealthResponse": ".types", "HealthResponseStatus": ".types", "HealthResponseVersion": ".types", @@ -677,11 +649,8 @@ "ListMentionsRequestDetails": ".mentions", "ListMentionsRequestEntityType": ".mentions", "ListMentionsRequestSentiment": ".mentions", - "ListMentionsRequestSrc": ".mentions", "ListRecommendationsRequestEntityType": ".recommendations", "ListRecommendationsRequestMentionClass": ".recommendations", - "ListRecommendationsRequestSrc": ".recommendations", - "ListRequestsTranscriptsRequestSrc": ".transcripts", "LookupEntitiesRequestType": ".entities", "MeResponse": ".types", "MeResponseCredentialKind": ".types", @@ -708,7 +677,6 @@ "MessageResponse": ".types", "MissedAlertChange": ".types", "MissingResultChange": ".types", - "MomentumEntitiesRequestSrc": ".entities", "Monitor": ".types", "MonitorAddTrackersResponse": ".types", "MonitorDeleteResponse": ".types", @@ -808,18 +776,14 @@ "RecommendationMediaSourceChannel": ".types", "ResolveCandidate": ".types", "ResolveCandidateMatch": ".types", - "ResolveEntitiesRequestSrc": ".entities", "ResolveEntitiesRequestType": ".entities", "ResolveSuggestion": ".types", "ResolveSuggestionMatch": ".types", "ResolveSuggestionReason": ".types", - "SearchEntitiesRequestSrc": ".entities", "SearchEntitiesRequestType": ".entities", - "SearchRequestType": ".types", "SearchResolveResponse": ".types", "SearchResolveResponseEntity": ".types", "SearchTranscriptsRequestSource": ".transcripts", - "SearchTranscriptsRequestSrc": ".transcripts", "ServiceUnavailableError": ".errors", "SignupSentResponse": ".types", "SignupSentResponseNext": ".types", @@ -830,7 +794,6 @@ "SpeakerIdentificationSubmittedResponseIdentificationEntity": ".types", "SpeakerIdentificationSubmittedResponseIdentificationStatus": ".types", "StaleMetadataChange": ".types", - "StatusTranscriptsRequestSrc": ".transcripts", "SubmitCorrectionsRequestAnchor": ".corrections", "SubmitCorrectionsRequestKind": ".corrections", "SubmitFeedbackRequestCorrectionsItem": ".feedback", @@ -882,6 +845,16 @@ "TranscriptPurchaseQuoteCharge": ".types", "TranscriptPurchaseQuoteChargeUnit": ".types", "TranscriptQuote": ".types", + "TranscriptRequest": ".types", + "TranscriptRequestCharge": ".types", + "TranscriptRequestChargeUnit": ".types", + "TranscriptRequestListResponse": ".types", + "TranscriptRequestListResponseRequestsItem": ".types", + "TranscriptRequestQuote": ".types", + "TranscriptRequestStage": ".types", + "TranscriptRequestState": ".types", + "TranscriptRequestStatus": ".types", + "TranscriptRequestSubmitResponse": ".types", "TranscriptResponse": ".types", "TranscriptResponseAccess": ".types", "TranscriptResponseAccessGate": ".types", @@ -913,16 +886,6 @@ "TranscriptSettings": ".types", "TranscriptSettingsQuality": ".types", "TranscriptVideo": ".types", - "TranscriptionListResponse": ".types", - "TranscriptionListResponseRequestsItem": ".types", - "TranscriptionRequest": ".types", - "TranscriptionRequestCharge": ".types", - "TranscriptionRequestChargeUnit": ".types", - "TranscriptionRequestQuote": ".types", - "TranscriptionRequestStage": ".types", - "TranscriptionRequestState": ".types", - "TranscriptionRequestStatus": ".types", - "TranscriptionSubmitResponse": ".types", "UnauthorizedError": ".errors", "UnprocessableEntityError": ".errors", "UpdateMonitorsRequestNotifyFrequency": ".monitors", @@ -950,7 +913,6 @@ "health": ".health", "me": ".me", "mentions": ".mentions", - "meta": ".meta", "monitors": ".monitors", "organizations": ".organizations", "people": ".people", @@ -997,7 +959,6 @@ def __dir__(): "BadRankingChange", "BadRequestError", "CaptionTrack", - "CaptionsTranscriptsRequestSrc", "ChannelCoverageResponse", "ChannelCoverageResponseChannel", "ChannelCoverageResponseChannelSourceMix", @@ -1049,8 +1010,6 @@ def __dir__(): "CorrectionAcceptedResponseKind", "CorrectionSeqMismatchResponse", "CountMentionsRequestMode", - "CountMentionsRequestSrc", - "CoverageChannelsRequestSrc", "CreateMonitorsRequestNotifyFrequency", "CreateTrackersRequestEntityType", "CreateTrackersRequestPersonMatchMode", @@ -1164,7 +1123,6 @@ def __dir__(): "ForbiddenError", "FreeformSuggestedChange", "GetTranscriptsRequestQuality", - "GetTranscriptsRequestSrc", "HealthResponse", "HealthResponseStatus", "HealthResponseVersion", @@ -1172,11 +1130,8 @@ def __dir__(): "ListMentionsRequestDetails", "ListMentionsRequestEntityType", "ListMentionsRequestSentiment", - "ListMentionsRequestSrc", "ListRecommendationsRequestEntityType", "ListRecommendationsRequestMentionClass", - "ListRecommendationsRequestSrc", - "ListRequestsTranscriptsRequestSrc", "LookupEntitiesRequestType", "MeResponse", "MeResponseCredentialKind", @@ -1203,7 +1158,6 @@ def __dir__(): "MessageResponse", "MissedAlertChange", "MissingResultChange", - "MomentumEntitiesRequestSrc", "Monitor", "MonitorAddTrackersResponse", "MonitorDeleteResponse", @@ -1303,18 +1257,14 @@ def __dir__(): "RecommendationMediaSourceChannel", "ResolveCandidate", "ResolveCandidateMatch", - "ResolveEntitiesRequestSrc", "ResolveEntitiesRequestType", "ResolveSuggestion", "ResolveSuggestionMatch", "ResolveSuggestionReason", - "SearchEntitiesRequestSrc", "SearchEntitiesRequestType", - "SearchRequestType", "SearchResolveResponse", "SearchResolveResponseEntity", "SearchTranscriptsRequestSource", - "SearchTranscriptsRequestSrc", "ServiceUnavailableError", "SignupSentResponse", "SignupSentResponseNext", @@ -1325,7 +1275,6 @@ def __dir__(): "SpeakerIdentificationSubmittedResponseIdentificationEntity", "SpeakerIdentificationSubmittedResponseIdentificationStatus", "StaleMetadataChange", - "StatusTranscriptsRequestSrc", "SubmitCorrectionsRequestAnchor", "SubmitCorrectionsRequestKind", "SubmitFeedbackRequestCorrectionsItem", @@ -1377,6 +1326,16 @@ def __dir__(): "TranscriptPurchaseQuoteCharge", "TranscriptPurchaseQuoteChargeUnit", "TranscriptQuote", + "TranscriptRequest", + "TranscriptRequestCharge", + "TranscriptRequestChargeUnit", + "TranscriptRequestListResponse", + "TranscriptRequestListResponseRequestsItem", + "TranscriptRequestQuote", + "TranscriptRequestStage", + "TranscriptRequestState", + "TranscriptRequestStatus", + "TranscriptRequestSubmitResponse", "TranscriptResponse", "TranscriptResponseAccess", "TranscriptResponseAccessGate", @@ -1408,16 +1367,6 @@ def __dir__(): "TranscriptSettings", "TranscriptSettingsQuality", "TranscriptVideo", - "TranscriptionListResponse", - "TranscriptionListResponseRequestsItem", - "TranscriptionRequest", - "TranscriptionRequestCharge", - "TranscriptionRequestChargeUnit", - "TranscriptionRequestQuote", - "TranscriptionRequestStage", - "TranscriptionRequestState", - "TranscriptionRequestStatus", - "TranscriptionSubmitResponse", "UnauthorizedError", "UnprocessableEntityError", "UpdateMonitorsRequestNotifyFrequency", @@ -1445,7 +1394,6 @@ def __dir__(): "health", "me", "mentions", - "meta", "monitors", "organizations", "people", diff --git a/src/arcmira/channels/__init__.py b/src/arcmira/channels/__init__.py index aeda67b..c84a158 100644 --- a/src/arcmira/channels/__init__.py +++ b/src/arcmira/channels/__init__.py @@ -6,7 +6,6 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import CoverageChannelsRequestSrc from . import guests, related, sponsors, videos from .guests import ListGuestsRequestIsAppearance, ListGuestsRequestMode, ListGuestsRequestOrder from .related import ( @@ -26,19 +25,15 @@ TopicsRelatedRequestMode, TopicsRelatedRequestOrder, ) - from .sponsors import ListSponsorsRequestSrc, ListSponsorsRequestStatus - from .videos import ListVideosRequestSrc + from .sponsors import ListSponsorsRequestStatus _dynamic_imports: typing.Dict[str, str] = { "ChannelsRelatedRequestIsAppearance": ".related", "ChannelsRelatedRequestMode": ".related", "ChannelsRelatedRequestOrder": ".related", - "CoverageChannelsRequestSrc": ".types", "ListGuestsRequestIsAppearance": ".guests", "ListGuestsRequestMode": ".guests", "ListGuestsRequestOrder": ".guests", - "ListSponsorsRequestSrc": ".sponsors", "ListSponsorsRequestStatus": ".sponsors", - "ListVideosRequestSrc": ".videos", "OrganizationsRelatedRequestIsAppearance": ".related", "OrganizationsRelatedRequestMode": ".related", "OrganizationsRelatedRequestOrder": ".related", @@ -83,13 +78,10 @@ def __dir__(): "ChannelsRelatedRequestIsAppearance", "ChannelsRelatedRequestMode", "ChannelsRelatedRequestOrder", - "CoverageChannelsRequestSrc", "ListGuestsRequestIsAppearance", "ListGuestsRequestMode", "ListGuestsRequestOrder", - "ListSponsorsRequestSrc", "ListSponsorsRequestStatus", - "ListVideosRequestSrc", "OrganizationsRelatedRequestIsAppearance", "OrganizationsRelatedRequestMode", "OrganizationsRelatedRequestOrder", diff --git a/src/arcmira/channels/client.py b/src/arcmira/channels/client.py index a375349..29c6d92 100644 --- a/src/arcmira/channels/client.py +++ b/src/arcmira/channels/client.py @@ -9,7 +9,6 @@ from ..types.channel_coverage_response import ChannelCoverageResponse from ..types.channel_page_response import ChannelPageResponse from .raw_client import AsyncRawChannelsClient, RawChannelsClient -from .types.coverage_channels_request_src import CoverageChannelsRequestSrc if typing.TYPE_CHECKING: from .guests.client import AsyncGuestsClient, GuestsClient @@ -39,11 +38,7 @@ def with_raw_response(self) -> RawChannelsClient: return self._raw_client def coverage( - self, - channel_id: str, - *, - src: typing.Optional[CoverageChannelsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> ChannelCoverageResponse: """ How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -53,9 +48,6 @@ def coverage( channel_id : str YouTube channel id, the UC... form. - src : typing.Optional[CoverageChannelsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -75,7 +67,7 @@ def coverage( channel_id="channel_id", ) """ - _response = self._raw_client.coverage(channel_id, src=src, request_options=request_options) + _response = self._raw_client.coverage(channel_id, request_options=request_options) return _response.data def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> ChannelPageResponse: @@ -163,11 +155,7 @@ def with_raw_response(self) -> AsyncRawChannelsClient: return self._raw_client async def coverage( - self, - channel_id: str, - *, - src: typing.Optional[CoverageChannelsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> ChannelCoverageResponse: """ How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -177,9 +165,6 @@ async def coverage( channel_id : str YouTube channel id, the UC... form. - src : typing.Optional[CoverageChannelsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -207,7 +192,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.coverage(channel_id, src=src, request_options=request_options) + _response = await self._raw_client.coverage(channel_id, request_options=request_options) return _response.data async def get(self, slug: str, *, request_options: typing.Optional[RequestOptions] = None) -> ChannelPageResponse: diff --git a/src/arcmira/channels/raw_client.py b/src/arcmira/channels/raw_client.py index 9551395..a81e914 100644 --- a/src/arcmira/channels/raw_client.py +++ b/src/arcmira/channels/raw_client.py @@ -20,7 +20,6 @@ from ..types.channel_coverage_response import ChannelCoverageResponse from ..types.channel_page_response import ChannelPageResponse from ..types.error import Error -from .types.coverage_channels_request_src import CoverageChannelsRequestSrc from pydantic import ValidationError @@ -29,11 +28,7 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def coverage( - self, - channel_id: str, - *, - src: typing.Optional[CoverageChannelsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[ChannelCoverageResponse]: """ How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -43,9 +38,6 @@ def coverage( channel_id : str YouTube channel id, the UC... form. - src : typing.Optional[CoverageChannelsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -57,9 +49,6 @@ def coverage( _response = self._client_wrapper.httpx_client.request( f"v1/channels/{encode_path_param(channel_id)}/coverage", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: @@ -273,11 +262,7 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def coverage( - self, - channel_id: str, - *, - src: typing.Optional[CoverageChannelsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[ChannelCoverageResponse]: """ How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free (0 rows). Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -287,9 +272,6 @@ async def coverage( channel_id : str YouTube channel id, the UC... form. - src : typing.Optional[CoverageChannelsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -301,9 +283,6 @@ async def coverage( _response = await self._client_wrapper.httpx_client.request( f"v1/channels/{encode_path_param(channel_id)}/coverage", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: diff --git a/src/arcmira/channels/sponsors/__init__.py b/src/arcmira/channels/sponsors/__init__.py index 60eaa53..8dcba57 100644 --- a/src/arcmira/channels/sponsors/__init__.py +++ b/src/arcmira/channels/sponsors/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ListSponsorsRequestSrc, ListSponsorsRequestStatus -_dynamic_imports: typing.Dict[str, str] = {"ListSponsorsRequestSrc": ".types", "ListSponsorsRequestStatus": ".types"} + from .types import ListSponsorsRequestStatus +_dynamic_imports: typing.Dict[str, str] = {"ListSponsorsRequestStatus": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -31,4 +31,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListSponsorsRequestSrc", "ListSponsorsRequestStatus"] +__all__ = ["ListSponsorsRequestStatus"] diff --git a/src/arcmira/channels/sponsors/client.py b/src/arcmira/channels/sponsors/client.py index 3f85cd4..6e0cd95 100644 --- a/src/arcmira/channels/sponsors/client.py +++ b/src/arcmira/channels/sponsors/client.py @@ -6,7 +6,6 @@ from ...core.request_options import RequestOptions from ...types.channel_sponsors_response import ChannelSponsorsResponse from .raw_client import AsyncRawSponsorsClient, RawSponsorsClient -from .types.list_sponsors_request_src import ListSponsorsRequestSrc from .types.list_sponsors_request_status import ListSponsorsRequestStatus @@ -32,7 +31,6 @@ def list( min_ad_reads: typing.Optional[int] = None, status: typing.Optional[ListSponsorsRequestStatus] = None, limit: typing.Optional[int] = None, - src: typing.Optional[ListSponsorsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> ChannelSponsorsResponse: """ @@ -52,9 +50,6 @@ def list( limit : typing.Optional[int] Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. - src : typing.Optional[ListSponsorsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -75,7 +70,7 @@ def list( ) """ _response = self._raw_client.list( - channel_id, min_ad_reads=min_ad_reads, status=status, limit=limit, src=src, request_options=request_options + channel_id, min_ad_reads=min_ad_reads, status=status, limit=limit, request_options=request_options ) return _response.data @@ -102,7 +97,6 @@ async def list( min_ad_reads: typing.Optional[int] = None, status: typing.Optional[ListSponsorsRequestStatus] = None, limit: typing.Optional[int] = None, - src: typing.Optional[ListSponsorsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> ChannelSponsorsResponse: """ @@ -122,9 +116,6 @@ async def list( limit : typing.Optional[int] Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. - src : typing.Optional[ListSponsorsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -153,6 +144,6 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.list( - channel_id, min_ad_reads=min_ad_reads, status=status, limit=limit, src=src, request_options=request_options + channel_id, min_ad_reads=min_ad_reads, status=status, limit=limit, request_options=request_options ) return _response.data diff --git a/src/arcmira/channels/sponsors/raw_client.py b/src/arcmira/channels/sponsors/raw_client.py index 8f01e48..7f85c2d 100644 --- a/src/arcmira/channels/sponsors/raw_client.py +++ b/src/arcmira/channels/sponsors/raw_client.py @@ -19,7 +19,6 @@ from ...errors.unauthorized_error import UnauthorizedError from ...types.channel_sponsors_response import ChannelSponsorsResponse from ...types.error import Error -from .types.list_sponsors_request_src import ListSponsorsRequestSrc from .types.list_sponsors_request_status import ListSponsorsRequestStatus from pydantic import ValidationError @@ -35,7 +34,6 @@ def list( min_ad_reads: typing.Optional[int] = None, status: typing.Optional[ListSponsorsRequestStatus] = None, limit: typing.Optional[int] = None, - src: typing.Optional[ListSponsorsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[ChannelSponsorsResponse]: """ @@ -55,9 +53,6 @@ def list( limit : typing.Optional[int] Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. - src : typing.Optional[ListSponsorsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -73,7 +68,6 @@ def list( "min_ad_reads": min_ad_reads, "status": status, "limit": limit, - "src": src, }, request_options=request_options, ) @@ -185,7 +179,6 @@ async def list( min_ad_reads: typing.Optional[int] = None, status: typing.Optional[ListSponsorsRequestStatus] = None, limit: typing.Optional[int] = None, - src: typing.Optional[ListSponsorsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[ChannelSponsorsResponse]: """ @@ -205,9 +198,6 @@ async def list( limit : typing.Optional[int] Sponsors to return. Default 100. Pro+ only; other plans receive the free slice. - src : typing.Optional[ListSponsorsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -223,7 +213,6 @@ async def list( "min_ad_reads": min_ad_reads, "status": status, "limit": limit, - "src": src, }, request_options=request_options, ) diff --git a/src/arcmira/channels/sponsors/types/__init__.py b/src/arcmira/channels/sponsors/types/__init__.py index ae4987f..0897622 100644 --- a/src/arcmira/channels/sponsors/types/__init__.py +++ b/src/arcmira/channels/sponsors/types/__init__.py @@ -6,12 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .list_sponsors_request_src import ListSponsorsRequestSrc from .list_sponsors_request_status import ListSponsorsRequestStatus -_dynamic_imports: typing.Dict[str, str] = { - "ListSponsorsRequestSrc": ".list_sponsors_request_src", - "ListSponsorsRequestStatus": ".list_sponsors_request_status", -} +_dynamic_imports: typing.Dict[str, str] = {"ListSponsorsRequestStatus": ".list_sponsors_request_status"} def __getattr__(attr_name: str) -> typing.Any: @@ -35,4 +31,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListSponsorsRequestSrc", "ListSponsorsRequestStatus"] +__all__ = ["ListSponsorsRequestStatus"] diff --git a/src/arcmira/channels/sponsors/types/list_sponsors_request_src.py b/src/arcmira/channels/sponsors/types/list_sponsors_request_src.py deleted file mode 100644 index d77e06d..0000000 --- a/src/arcmira/channels/sponsors/types/list_sponsors_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListSponsorsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/channels/types/__init__.py b/src/arcmira/channels/types/__init__.py deleted file mode 100644 index 6460332..0000000 --- a/src/arcmira/channels/types/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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 .coverage_channels_request_src import CoverageChannelsRequestSrc -_dynamic_imports: typing.Dict[str, str] = {"CoverageChannelsRequestSrc": ".coverage_channels_request_src"} - - -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__ = ["CoverageChannelsRequestSrc"] diff --git a/src/arcmira/channels/types/coverage_channels_request_src.py b/src/arcmira/channels/types/coverage_channels_request_src.py deleted file mode 100644 index abfa9da..0000000 --- a/src/arcmira/channels/types/coverage_channels_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CoverageChannelsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/channels/videos/__init__.py b/src/arcmira/channels/videos/__init__.py index a17a5a3..dadae70 100644 --- a/src/arcmira/channels/videos/__init__.py +++ b/src/arcmira/channels/videos/__init__.py @@ -1,34 +1,3 @@ # 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 ListVideosRequestSrc -_dynamic_imports: typing.Dict[str, str] = {"ListVideosRequestSrc": ".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__ = ["ListVideosRequestSrc"] diff --git a/src/arcmira/channels/videos/client.py b/src/arcmira/channels/videos/client.py index 88434c3..5c5fb3d 100644 --- a/src/arcmira/channels/videos/client.py +++ b/src/arcmira/channels/videos/client.py @@ -8,7 +8,6 @@ from ...types.channel_videos_response import ChannelVideosResponse from ...types.channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem from .raw_client import AsyncRawVideosClient, RawVideosClient -from .types.list_videos_request_src import ListVideosRequestSrc class VideosClient: @@ -34,7 +33,6 @@ def list( cursor: typing.Optional[str] = None, published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, - src: typing.Optional[ListVideosRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ @@ -57,9 +55,6 @@ def list( published_before : typing.Optional[str] ISO date. Only videos published before this day. - src : typing.Optional[ListVideosRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -90,7 +85,6 @@ def list( cursor=cursor, published_after=published_after, published_before=published_before, - src=src, request_options=request_options, ) @@ -118,7 +112,6 @@ async def list( cursor: typing.Optional[str] = None, published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, - src: typing.Optional[ListVideosRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ @@ -141,9 +134,6 @@ async def list( published_before : typing.Optional[str] ISO date. Only videos published before this day. - src : typing.Optional[ListVideosRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -183,6 +173,5 @@ async def main() -> None: cursor=cursor, published_after=published_after, published_before=published_before, - src=src, request_options=request_options, ) diff --git a/src/arcmira/channels/videos/raw_client.py b/src/arcmira/channels/videos/raw_client.py index 6b3388e..30be0cc 100644 --- a/src/arcmira/channels/videos/raw_client.py +++ b/src/arcmira/channels/videos/raw_client.py @@ -20,7 +20,6 @@ from ...types.channel_videos_response import ChannelVideosResponse from ...types.channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem from ...types.error import Error -from .types.list_videos_request_src import ListVideosRequestSrc from pydantic import ValidationError @@ -36,7 +35,6 @@ def list( cursor: typing.Optional[str] = None, published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, - src: typing.Optional[ListVideosRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ @@ -59,9 +57,6 @@ def list( published_before : typing.Optional[str] ISO date. Only videos published before this day. - src : typing.Optional[ListVideosRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -78,7 +73,6 @@ def list( "cursor": cursor, "published_after": published_after, "published_before": published_before, - "src": src, }, request_options=request_options, ) @@ -100,7 +94,6 @@ def list( cursor=_parsed_next, published_after=published_after, published_before=published_before, - src=src, request_options=request_options, ) return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) @@ -203,7 +196,6 @@ async def list( cursor: typing.Optional[str] = None, published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, - src: typing.Optional[ListVideosRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ @@ -226,9 +218,6 @@ async def list( published_before : typing.Optional[str] ISO date. Only videos published before this day. - src : typing.Optional[ListVideosRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -245,7 +234,6 @@ async def list( "cursor": cursor, "published_after": published_after, "published_before": published_before, - "src": src, }, request_options=request_options, ) @@ -269,7 +257,6 @@ async def _get_next(): cursor=_parsed_next, published_after=published_after, published_before=published_before, - src=src, request_options=request_options, ) diff --git a/src/arcmira/channels/videos/types/__init__.py b/src/arcmira/channels/videos/types/__init__.py deleted file mode 100644 index 4f8b6aa..0000000 --- a/src/arcmira/channels/videos/types/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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 .list_videos_request_src import ListVideosRequestSrc -_dynamic_imports: typing.Dict[str, str] = {"ListVideosRequestSrc": ".list_videos_request_src"} - - -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__ = ["ListVideosRequestSrc"] diff --git a/src/arcmira/channels/videos/types/list_videos_request_src.py b/src/arcmira/channels/videos/types/list_videos_request_src.py deleted file mode 100644 index f8ddbb2..0000000 --- a/src/arcmira/channels/videos/types/list_videos_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListVideosRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/client.py b/src/arcmira/client.py index 5aec0c2..384be17 100644 --- a/src/arcmira/client.py +++ b/src/arcmira/client.py @@ -8,11 +8,7 @@ import httpx from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.logging import LogConfig, Logger -from .core.request_options import RequestOptions from .environment import ArcmiraEnvironment -from .raw_client import AsyncRawArcmira, RawArcmira -from .types.search_request_type import SearchRequestType -from .types.search_resolve_response import SearchResolveResponse if typing.TYPE_CHECKING: from .channels.client import AsyncChannelsClient, ChannelsClient @@ -22,7 +18,6 @@ from .health.client import AsyncHealthClient, HealthClient from .me.client import AsyncMeClient, MeClient from .mentions.client import AsyncMentionsClient, MentionsClient - from .meta.client import AsyncMetaClient, MetaClient from .monitors.client import AsyncMonitorsClient, MonitorsClient from .organizations.client import AsyncOrganizationsClient, OrganizationsClient from .people.client import AsyncPeopleClient, PeopleClient @@ -118,9 +113,7 @@ def __init__( max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, ) - self._raw_client = RawArcmira(client_wrapper=self._client_wrapper) self._health: typing.Optional[HealthClient] = None - self._meta: typing.Optional[MetaClient] = None self._me: typing.Optional[MeClient] = None self._entities: typing.Optional[EntitiesClient] = None self._mentions: typing.Optional[MentionsClient] = None @@ -137,57 +130,6 @@ def __init__( self._team: typing.Optional[TeamClient] = None self._corrections: typing.Optional[CorrectionsClient] = None - @property - def with_raw_response(self) -> RawArcmira: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawArcmira - """ - return self._raw_client - - def search( - self, - *, - q: str, - type: typing.Optional[SearchRequestType] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> SearchResolveResponse: - """ - Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. - - Parameters - ---------- - q : str - Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. - - type : typing.Optional[SearchRequestType] - Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SearchResolveResponse - Success - - Examples - -------- - from arcmira import Arcmira - - client = Arcmira( - api_key="YOUR_API_KEY", - ) - client.search( - q="q", - ) - """ - _response = self._raw_client.search(q=q, type=type, request_options=request_options) - return _response.data - @property def health(self): if self._health is None: @@ -196,14 +138,6 @@ def health(self): self._health = HealthClient(client_wrapper=self._client_wrapper) return self._health - @property - def meta(self): - if self._meta is None: - from .meta.client import MetaClient # noqa: E402 - - self._meta = MetaClient(client_wrapper=self._client_wrapper) - return self._meta - @property def me(self): if self._me is None: @@ -430,9 +364,7 @@ def __init__( max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, ) - self._raw_client = AsyncRawArcmira(client_wrapper=self._client_wrapper) self._health: typing.Optional[AsyncHealthClient] = None - self._meta: typing.Optional[AsyncMetaClient] = None self._me: typing.Optional[AsyncMeClient] = None self._entities: typing.Optional[AsyncEntitiesClient] = None self._mentions: typing.Optional[AsyncMentionsClient] = None @@ -449,65 +381,6 @@ def __init__( self._team: typing.Optional[AsyncTeamClient] = None self._corrections: typing.Optional[AsyncCorrectionsClient] = None - @property - def with_raw_response(self) -> AsyncRawArcmira: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawArcmira - """ - return self._raw_client - - async def search( - self, - *, - q: str, - type: typing.Optional[SearchRequestType] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> SearchResolveResponse: - """ - Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. - - Parameters - ---------- - q : str - Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. - - type : typing.Optional[SearchRequestType] - Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SearchResolveResponse - Success - - Examples - -------- - import asyncio - - from arcmira import AsyncArcmira - - client = AsyncArcmira( - api_key="YOUR_API_KEY", - ) - - - async def main() -> None: - await client.search( - q="q", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.search(q=q, type=type, request_options=request_options) - return _response.data - @property def health(self): if self._health is None: @@ -516,14 +389,6 @@ def health(self): self._health = AsyncHealthClient(client_wrapper=self._client_wrapper) return self._health - @property - def meta(self): - if self._meta is None: - from .meta.client import AsyncMetaClient # noqa: E402 - - self._meta = AsyncMetaClient(client_wrapper=self._client_wrapper) - return self._meta - @property def me(self): if self._me is None: diff --git a/src/arcmira/entities/__init__.py b/src/arcmira/entities/__init__.py index acf9d5a..4d0f946 100644 --- a/src/arcmira/entities/__init__.py +++ b/src/arcmira/entities/__init__.py @@ -6,28 +6,16 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ( - LookupEntitiesRequestType, - MomentumEntitiesRequestSrc, - ResolveEntitiesRequestSrc, - ResolveEntitiesRequestType, - SearchEntitiesRequestSrc, - SearchEntitiesRequestType, - ) + from .types import LookupEntitiesRequestType, ResolveEntitiesRequestType, SearchEntitiesRequestType from . import mentions, recommendations - from .mentions import ListMentionsRequestDetails, ListMentionsRequestSentiment, ListMentionsRequestSrc - from .recommendations import ListRecommendationsRequestMentionClass, ListRecommendationsRequestSrc + from .mentions import ListMentionsRequestDetails, ListMentionsRequestSentiment + from .recommendations import ListRecommendationsRequestMentionClass _dynamic_imports: typing.Dict[str, str] = { "ListMentionsRequestDetails": ".mentions", "ListMentionsRequestSentiment": ".mentions", - "ListMentionsRequestSrc": ".mentions", "ListRecommendationsRequestMentionClass": ".recommendations", - "ListRecommendationsRequestSrc": ".recommendations", "LookupEntitiesRequestType": ".types", - "MomentumEntitiesRequestSrc": ".types", - "ResolveEntitiesRequestSrc": ".types", "ResolveEntitiesRequestType": ".types", - "SearchEntitiesRequestSrc": ".types", "SearchEntitiesRequestType": ".types", "mentions": ".mentions", "recommendations": ".recommendations", @@ -58,14 +46,9 @@ def __dir__(): __all__ = [ "ListMentionsRequestDetails", "ListMentionsRequestSentiment", - "ListMentionsRequestSrc", "ListRecommendationsRequestMentionClass", - "ListRecommendationsRequestSrc", "LookupEntitiesRequestType", - "MomentumEntitiesRequestSrc", - "ResolveEntitiesRequestSrc", "ResolveEntitiesRequestType", - "SearchEntitiesRequestSrc", "SearchEntitiesRequestType", "mentions", "recommendations", diff --git a/src/arcmira/entities/client.py b/src/arcmira/entities/client.py index 9318184..b14745e 100644 --- a/src/arcmira/entities/client.py +++ b/src/arcmira/entities/client.py @@ -14,10 +14,7 @@ from ..types.entity_search_response import EntitySearchResponse from .raw_client import AsyncRawEntitiesClient, RawEntitiesClient from .types.lookup_entities_request_type import LookupEntitiesRequestType -from .types.momentum_entities_request_src import MomentumEntitiesRequestSrc -from .types.resolve_entities_request_src import ResolveEntitiesRequestSrc from .types.resolve_entities_request_type import ResolveEntitiesRequestType -from .types.search_entities_request_src import SearchEntitiesRequestSrc from .types.search_entities_request_type import SearchEntitiesRequestType if typing.TYPE_CHECKING: @@ -50,7 +47,6 @@ def search( type: typing.Optional[SearchEntitiesRequestType] = None, has_recommendations_data: typing.Optional[bool] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> EntitySearchResponse: """ @@ -66,9 +62,6 @@ def search( limit : typing.Optional[int] - src : typing.Optional[SearchEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -93,7 +86,6 @@ def search( type=type, has_recommendations_data=has_recommendations_data, limit=limit, - src=src, request_options=request_options, ) return _response.data @@ -105,7 +97,6 @@ def resolve( type: typing.Optional[ResolveEntitiesRequestType] = None, limit: typing.Optional[int] = None, context: typing.Optional[str] = None, - src: typing.Optional[ResolveEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> EntityResolveResponse: """ @@ -125,9 +116,6 @@ def resolve( context : typing.Optional[str] What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. - src : typing.Optional[ResolveEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -148,7 +136,7 @@ def resolve( ) """ _response = self._raw_client.resolve( - q=q, type=type, limit=limit, context=context, src=src, request_options=request_options + q=q, type=type, limit=limit, context=context, request_options=request_options ) return _response.data @@ -253,13 +241,7 @@ def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = Non _response = self._raw_client.get(id, request_options=request_options) return _response.data - def momentum( - self, - id: str, - *, - src: typing.Optional[MomentumEntitiesRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> EntityMomentumResponse: + def momentum(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> EntityMomentumResponse: """ Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -268,9 +250,6 @@ def momentum( id : str Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. - src : typing.Optional[MomentumEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -290,7 +269,7 @@ def momentum( id="id", ) """ - _response = self._raw_client.momentum(id, src=src, request_options=request_options) + _response = self._raw_client.momentum(id, request_options=request_options) return _response.data @property @@ -335,7 +314,6 @@ async def search( type: typing.Optional[SearchEntitiesRequestType] = None, has_recommendations_data: typing.Optional[bool] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> EntitySearchResponse: """ @@ -351,9 +329,6 @@ async def search( limit : typing.Optional[int] - src : typing.Optional[SearchEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -386,7 +361,6 @@ async def main() -> None: type=type, has_recommendations_data=has_recommendations_data, limit=limit, - src=src, request_options=request_options, ) return _response.data @@ -398,7 +372,6 @@ async def resolve( type: typing.Optional[ResolveEntitiesRequestType] = None, limit: typing.Optional[int] = None, context: typing.Optional[str] = None, - src: typing.Optional[ResolveEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> EntityResolveResponse: """ @@ -418,9 +391,6 @@ async def resolve( context : typing.Optional[str] What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. - src : typing.Optional[ResolveEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -449,7 +419,7 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.resolve( - q=q, type=type, limit=limit, context=context, src=src, request_options=request_options + q=q, type=type, limit=limit, context=context, request_options=request_options ) return _response.data @@ -579,11 +549,7 @@ async def main() -> None: return _response.data async def momentum( - self, - id: str, - *, - src: typing.Optional[MomentumEntitiesRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> EntityMomentumResponse: """ Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -593,9 +559,6 @@ async def momentum( id : str Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. - src : typing.Optional[MomentumEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -623,7 +586,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.momentum(id, src=src, request_options=request_options) + _response = await self._raw_client.momentum(id, request_options=request_options) return _response.data @property diff --git a/src/arcmira/entities/mentions/__init__.py b/src/arcmira/entities/mentions/__init__.py index ef945ab..9b17658 100644 --- a/src/arcmira/entities/mentions/__init__.py +++ b/src/arcmira/entities/mentions/__init__.py @@ -6,11 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ListMentionsRequestDetails, ListMentionsRequestSentiment, ListMentionsRequestSrc + from .types import ListMentionsRequestDetails, ListMentionsRequestSentiment _dynamic_imports: typing.Dict[str, str] = { "ListMentionsRequestDetails": ".types", "ListMentionsRequestSentiment": ".types", - "ListMentionsRequestSrc": ".types", } @@ -35,4 +34,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListMentionsRequestDetails", "ListMentionsRequestSentiment", "ListMentionsRequestSrc"] +__all__ = ["ListMentionsRequestDetails", "ListMentionsRequestSentiment"] diff --git a/src/arcmira/entities/mentions/client.py b/src/arcmira/entities/mentions/client.py index 78d3b3c..42b314f 100644 --- a/src/arcmira/entities/mentions/client.py +++ b/src/arcmira/entities/mentions/client.py @@ -10,7 +10,6 @@ from .raw_client import AsyncRawMentionsClient, RawMentionsClient from .types.list_mentions_request_details import ListMentionsRequestDetails from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment -from .types.list_mentions_request_src import ListMentionsRequestSrc class MentionsClient: @@ -42,7 +41,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ @@ -74,9 +72,6 @@ def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -113,7 +108,6 @@ def list( date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) @@ -147,7 +141,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ @@ -179,9 +172,6 @@ async def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -227,6 +217,5 @@ async def main() -> None: date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) diff --git a/src/arcmira/entities/mentions/raw_client.py b/src/arcmira/entities/mentions/raw_client.py index f3367c8..50f18a5 100644 --- a/src/arcmira/entities/mentions/raw_client.py +++ b/src/arcmira/entities/mentions/raw_client.py @@ -22,7 +22,6 @@ from ...types.mention_list_response import MentionListResponse from .types.list_mentions_request_details import ListMentionsRequestDetails from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment -from .types.list_mentions_request_src import ListMentionsRequestSrc from pydantic import ValidationError @@ -44,7 +43,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ @@ -76,9 +74,6 @@ def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -101,7 +96,6 @@ def list( "date_from": date_from, "date_to": date_to, "details": details, - "src": src, }, request_options=request_options, ) @@ -129,7 +123,6 @@ def list( date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) @@ -238,7 +231,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ @@ -270,9 +262,6 @@ async def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -295,7 +284,6 @@ async def list( "date_from": date_from, "date_to": date_to, "details": details, - "src": src, }, request_options=request_options, ) @@ -325,7 +313,6 @@ async def _get_next(): date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) diff --git a/src/arcmira/entities/mentions/types/__init__.py b/src/arcmira/entities/mentions/types/__init__.py index 429126f..274ea68 100644 --- a/src/arcmira/entities/mentions/types/__init__.py +++ b/src/arcmira/entities/mentions/types/__init__.py @@ -8,11 +8,9 @@ if typing.TYPE_CHECKING: from .list_mentions_request_details import ListMentionsRequestDetails from .list_mentions_request_sentiment import ListMentionsRequestSentiment - from .list_mentions_request_src import ListMentionsRequestSrc _dynamic_imports: typing.Dict[str, str] = { "ListMentionsRequestDetails": ".list_mentions_request_details", "ListMentionsRequestSentiment": ".list_mentions_request_sentiment", - "ListMentionsRequestSrc": ".list_mentions_request_src", } @@ -37,4 +35,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListMentionsRequestDetails", "ListMentionsRequestSentiment", "ListMentionsRequestSrc"] +__all__ = ["ListMentionsRequestDetails", "ListMentionsRequestSentiment"] diff --git a/src/arcmira/entities/mentions/types/list_mentions_request_src.py b/src/arcmira/entities/mentions/types/list_mentions_request_src.py deleted file mode 100644 index 84409bd..0000000 --- a/src/arcmira/entities/mentions/types/list_mentions_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListMentionsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/raw_client.py b/src/arcmira/entities/raw_client.py index 3cc4e3a..dc355db 100644 --- a/src/arcmira/entities/raw_client.py +++ b/src/arcmira/entities/raw_client.py @@ -25,10 +25,7 @@ from ..types.entity_search_response import EntitySearchResponse from ..types.error import Error from .types.lookup_entities_request_type import LookupEntitiesRequestType -from .types.momentum_entities_request_src import MomentumEntitiesRequestSrc -from .types.resolve_entities_request_src import ResolveEntitiesRequestSrc from .types.resolve_entities_request_type import ResolveEntitiesRequestType -from .types.search_entities_request_src import SearchEntitiesRequestSrc from .types.search_entities_request_type import SearchEntitiesRequestType from pydantic import ValidationError @@ -44,7 +41,6 @@ def search( type: typing.Optional[SearchEntitiesRequestType] = None, has_recommendations_data: typing.Optional[bool] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[EntitySearchResponse]: """ @@ -60,9 +56,6 @@ def search( limit : typing.Optional[int] - src : typing.Optional[SearchEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -79,7 +72,6 @@ def search( "type": type, "has_recommendations_data": has_recommendations_data, "limit": limit, - "src": src, }, request_options=request_options, ) @@ -186,7 +178,6 @@ def resolve( type: typing.Optional[ResolveEntitiesRequestType] = None, limit: typing.Optional[int] = None, context: typing.Optional[str] = None, - src: typing.Optional[ResolveEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[EntityResolveResponse]: """ @@ -206,9 +197,6 @@ def resolve( context : typing.Optional[str] What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. - src : typing.Optional[ResolveEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -225,7 +213,6 @@ def resolve( "type": type, "limit": limit, "context": context, - "src": src, }, request_options=request_options, ) @@ -680,11 +667,7 @@ def get( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def momentum( - self, - id: str, - *, - src: typing.Optional[MomentumEntitiesRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[EntityMomentumResponse]: """ Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -694,9 +677,6 @@ def momentum( id : str Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. - src : typing.Optional[MomentumEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -708,9 +688,6 @@ def momentum( _response = self._client_wrapper.httpx_client.request( f"v1/entities/{encode_path_param(id)}/momentum", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: @@ -821,7 +798,6 @@ async def search( type: typing.Optional[SearchEntitiesRequestType] = None, has_recommendations_data: typing.Optional[bool] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[EntitySearchResponse]: """ @@ -837,9 +813,6 @@ async def search( limit : typing.Optional[int] - src : typing.Optional[SearchEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -856,7 +829,6 @@ async def search( "type": type, "has_recommendations_data": has_recommendations_data, "limit": limit, - "src": src, }, request_options=request_options, ) @@ -963,7 +935,6 @@ async def resolve( type: typing.Optional[ResolveEntitiesRequestType] = None, limit: typing.Optional[int] = None, context: typing.Optional[str] = None, - src: typing.Optional[ResolveEntitiesRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[EntityResolveResponse]: """ @@ -983,9 +954,6 @@ async def resolve( context : typing.Optional[str] What the user said about the name, in their words ("the startup bank", "Canada's prime minister", "on My First Million"). Ranks candidates by their description and by the episodes they share with what the context names; a clear winner comes back as suggested with reason context. - src : typing.Optional[ResolveEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1002,7 +970,6 @@ async def resolve( "type": type, "limit": limit, "context": context, - "src": src, }, request_options=request_options, ) @@ -1457,11 +1424,7 @@ async def get( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def momentum( - self, - id: str, - *, - src: typing.Optional[MomentumEntitiesRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[EntityMomentumResponse]: """ Mentions in the last 7 and 30 days against the prior 30, an absolute-delta verdict (accelerating, flat, fading, none), the newest media date, and the top shows in the window. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic; otherwise that field is absent and access names the gate. Bills one row. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. @@ -1471,9 +1434,6 @@ async def momentum( id : str Entity id, ent_{n} or the numeric id. Merged ids follow their redirect. - src : typing.Optional[MomentumEntitiesRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1485,9 +1445,6 @@ async def momentum( _response = await self._client_wrapper.httpx_client.request( f"v1/entities/{encode_path_param(id)}/momentum", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: diff --git a/src/arcmira/entities/recommendations/__init__.py b/src/arcmira/entities/recommendations/__init__.py index 4e59ee4..e888766 100644 --- a/src/arcmira/entities/recommendations/__init__.py +++ b/src/arcmira/entities/recommendations/__init__.py @@ -6,11 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ListRecommendationsRequestMentionClass, ListRecommendationsRequestSrc -_dynamic_imports: typing.Dict[str, str] = { - "ListRecommendationsRequestMentionClass": ".types", - "ListRecommendationsRequestSrc": ".types", -} + from .types import ListRecommendationsRequestMentionClass +_dynamic_imports: typing.Dict[str, str] = {"ListRecommendationsRequestMentionClass": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -34,4 +31,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListRecommendationsRequestMentionClass", "ListRecommendationsRequestSrc"] +__all__ = ["ListRecommendationsRequestMentionClass"] diff --git a/src/arcmira/entities/recommendations/client.py b/src/arcmira/entities/recommendations/client.py index 0ec61a6..ea9a9fe 100644 --- a/src/arcmira/entities/recommendations/client.py +++ b/src/arcmira/entities/recommendations/client.py @@ -9,7 +9,6 @@ from ...types.recommendation_list_response import RecommendationListResponse from .raw_client import AsyncRawRecommendationsClient, RawRecommendationsClient from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass -from .types.list_recommendations_request_src import ListRecommendationsRequestSrc class RecommendationsClient: @@ -40,7 +39,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ @@ -70,9 +68,6 @@ def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -108,7 +103,6 @@ def list( date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) @@ -141,7 +135,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ @@ -171,9 +164,6 @@ async def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -218,6 +208,5 @@ async def main() -> None: date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) diff --git a/src/arcmira/entities/recommendations/raw_client.py b/src/arcmira/entities/recommendations/raw_client.py index 4b50fd5..e6befdf 100644 --- a/src/arcmira/entities/recommendations/raw_client.py +++ b/src/arcmira/entities/recommendations/raw_client.py @@ -21,7 +21,6 @@ from ...types.recommendation import Recommendation from ...types.recommendation_list_response import RecommendationListResponse from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass -from .types.list_recommendations_request_src import ListRecommendationsRequestSrc from pydantic import ValidationError @@ -42,7 +41,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ @@ -72,9 +70,6 @@ def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -96,7 +91,6 @@ def list( "date_from": date_from, "date_to": date_to, "include_disputed": include_disputed, - "src": src, }, request_options=request_options, ) @@ -123,7 +117,6 @@ def list( date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) @@ -231,7 +224,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ @@ -261,9 +253,6 @@ async def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -285,7 +274,6 @@ async def list( "date_from": date_from, "date_to": date_to, "include_disputed": include_disputed, - "src": src, }, request_options=request_options, ) @@ -314,7 +302,6 @@ async def _get_next(): date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) diff --git a/src/arcmira/entities/recommendations/types/__init__.py b/src/arcmira/entities/recommendations/types/__init__.py index c3b247a..4df49ab 100644 --- a/src/arcmira/entities/recommendations/types/__init__.py +++ b/src/arcmira/entities/recommendations/types/__init__.py @@ -7,10 +7,8 @@ if typing.TYPE_CHECKING: from .list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass - from .list_recommendations_request_src import ListRecommendationsRequestSrc _dynamic_imports: typing.Dict[str, str] = { - "ListRecommendationsRequestMentionClass": ".list_recommendations_request_mention_class", - "ListRecommendationsRequestSrc": ".list_recommendations_request_src", + "ListRecommendationsRequestMentionClass": ".list_recommendations_request_mention_class" } @@ -35,4 +33,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListRecommendationsRequestMentionClass", "ListRecommendationsRequestSrc"] +__all__ = ["ListRecommendationsRequestMentionClass"] diff --git a/src/arcmira/entities/recommendations/types/list_recommendations_request_src.py b/src/arcmira/entities/recommendations/types/list_recommendations_request_src.py deleted file mode 100644 index a8ce855..0000000 --- a/src/arcmira/entities/recommendations/types/list_recommendations_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListRecommendationsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/__init__.py b/src/arcmira/entities/types/__init__.py index 6f4e96f..7c6b7e8 100644 --- a/src/arcmira/entities/types/__init__.py +++ b/src/arcmira/entities/types/__init__.py @@ -7,17 +7,11 @@ if typing.TYPE_CHECKING: from .lookup_entities_request_type import LookupEntitiesRequestType - from .momentum_entities_request_src import MomentumEntitiesRequestSrc - from .resolve_entities_request_src import ResolveEntitiesRequestSrc from .resolve_entities_request_type import ResolveEntitiesRequestType - from .search_entities_request_src import SearchEntitiesRequestSrc from .search_entities_request_type import SearchEntitiesRequestType _dynamic_imports: typing.Dict[str, str] = { "LookupEntitiesRequestType": ".lookup_entities_request_type", - "MomentumEntitiesRequestSrc": ".momentum_entities_request_src", - "ResolveEntitiesRequestSrc": ".resolve_entities_request_src", "ResolveEntitiesRequestType": ".resolve_entities_request_type", - "SearchEntitiesRequestSrc": ".search_entities_request_src", "SearchEntitiesRequestType": ".search_entities_request_type", } @@ -43,11 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = [ - "LookupEntitiesRequestType", - "MomentumEntitiesRequestSrc", - "ResolveEntitiesRequestSrc", - "ResolveEntitiesRequestType", - "SearchEntitiesRequestSrc", - "SearchEntitiesRequestType", -] +__all__ = ["LookupEntitiesRequestType", "ResolveEntitiesRequestType", "SearchEntitiesRequestType"] diff --git a/src/arcmira/entities/types/momentum_entities_request_src.py b/src/arcmira/entities/types/momentum_entities_request_src.py deleted file mode 100644 index 078afe7..0000000 --- a/src/arcmira/entities/types/momentum_entities_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -MomentumEntitiesRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/resolve_entities_request_src.py b/src/arcmira/entities/types/resolve_entities_request_src.py deleted file mode 100644 index c798156..0000000 --- a/src/arcmira/entities/types/resolve_entities_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ResolveEntitiesRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/entities/types/search_entities_request_src.py b/src/arcmira/entities/types/search_entities_request_src.py deleted file mode 100644 index 99be0cc..0000000 --- a/src/arcmira/entities/types/search_entities_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -SearchEntitiesRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/mentions/__init__.py b/src/arcmira/mentions/__init__.py index 96efbee..2c840a8 100644 --- a/src/arcmira/mentions/__init__.py +++ b/src/arcmira/mentions/__init__.py @@ -8,19 +8,15 @@ if typing.TYPE_CHECKING: from .types import ( CountMentionsRequestMode, - CountMentionsRequestSrc, ListMentionsRequestDetails, ListMentionsRequestEntityType, ListMentionsRequestSentiment, - ListMentionsRequestSrc, ) _dynamic_imports: typing.Dict[str, str] = { "CountMentionsRequestMode": ".types", - "CountMentionsRequestSrc": ".types", "ListMentionsRequestDetails": ".types", "ListMentionsRequestEntityType": ".types", "ListMentionsRequestSentiment": ".types", - "ListMentionsRequestSrc": ".types", } @@ -47,9 +43,7 @@ def __dir__(): __all__ = [ "CountMentionsRequestMode", - "CountMentionsRequestSrc", "ListMentionsRequestDetails", "ListMentionsRequestEntityType", "ListMentionsRequestSentiment", - "ListMentionsRequestSrc", ] diff --git a/src/arcmira/mentions/client.py b/src/arcmira/mentions/client.py index a91ecaa..c799b6f 100644 --- a/src/arcmira/mentions/client.py +++ b/src/arcmira/mentions/client.py @@ -10,11 +10,9 @@ from ..types.mention_list_response import MentionListResponse from .raw_client import AsyncRawMentionsClient, RawMentionsClient from .types.count_mentions_request_mode import CountMentionsRequestMode -from .types.count_mentions_request_src import CountMentionsRequestSrc from .types.list_mentions_request_details import ListMentionsRequestDetails from .types.list_mentions_request_entity_type import ListMentionsRequestEntityType from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment -from .types.list_mentions_request_src import ListMentionsRequestSrc class MentionsClient: @@ -48,7 +46,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ @@ -83,9 +80,6 @@ def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -122,7 +116,6 @@ def list( date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) @@ -137,7 +130,6 @@ def count( published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, limit: typing.Optional[int] = None, - src: typing.Optional[CountMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> MentionCountsResponse: """ @@ -169,9 +161,6 @@ def count( limit : typing.Optional[int] Rows in the ranked table, 1 to 40. Default 20. - src : typing.Optional[CountMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -198,7 +187,6 @@ def count( published_after=published_after, published_before=published_before, limit=limit, - src=src, request_options=request_options, ) return _response.data @@ -235,7 +223,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ @@ -270,9 +257,6 @@ async def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -318,7 +302,6 @@ async def main() -> None: date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) @@ -333,7 +316,6 @@ async def count( published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, limit: typing.Optional[int] = None, - src: typing.Optional[CountMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> MentionCountsResponse: """ @@ -365,9 +347,6 @@ async def count( limit : typing.Optional[int] Rows in the ranked table, 1 to 40. Default 20. - src : typing.Optional[CountMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -402,7 +381,6 @@ async def main() -> None: published_after=published_after, published_before=published_before, limit=limit, - src=src, request_options=request_options, ) return _response.data diff --git a/src/arcmira/mentions/raw_client.py b/src/arcmira/mentions/raw_client.py index f6d703e..e6f7224 100644 --- a/src/arcmira/mentions/raw_client.py +++ b/src/arcmira/mentions/raw_client.py @@ -22,11 +22,9 @@ from ..types.mention_counts_response import MentionCountsResponse from ..types.mention_list_response import MentionListResponse from .types.count_mentions_request_mode import CountMentionsRequestMode -from .types.count_mentions_request_src import CountMentionsRequestSrc from .types.list_mentions_request_details import ListMentionsRequestDetails from .types.list_mentions_request_entity_type import ListMentionsRequestEntityType from .types.list_mentions_request_sentiment import ListMentionsRequestSentiment -from .types.list_mentions_request_src import ListMentionsRequestSrc from pydantic import ValidationError @@ -50,7 +48,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ @@ -85,9 +82,6 @@ def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -113,7 +107,6 @@ def list( "date_from": date_from, "date_to": date_to, "details": details, - "src": src, }, request_options=request_options, ) @@ -143,7 +136,6 @@ def list( date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) @@ -244,7 +236,6 @@ def count( published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, limit: typing.Optional[int] = None, - src: typing.Optional[CountMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[MentionCountsResponse]: """ @@ -276,9 +267,6 @@ def count( limit : typing.Optional[int] Rows in the ranked table, 1 to 40. Default 20. - src : typing.Optional[CountMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -299,7 +287,6 @@ def count( "published_after": published_after, "published_before": published_before, "limit": limit, - "src": src, }, request_options=request_options, ) @@ -420,7 +407,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, details: typing.Optional[ListMentionsRequestDetails] = None, - src: typing.Optional[ListMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ @@ -455,9 +441,6 @@ async def list( details : typing.Optional[ListMentionsRequestDetails] - src : typing.Optional[ListMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -483,7 +466,6 @@ async def list( "date_from": date_from, "date_to": date_to, "details": details, - "src": src, }, request_options=request_options, ) @@ -515,7 +497,6 @@ async def _get_next(): date_from=date_from, date_to=date_to, details=details, - src=src, request_options=request_options, ) @@ -617,7 +598,6 @@ async def count( published_after: typing.Optional[str] = None, published_before: typing.Optional[str] = None, limit: typing.Optional[int] = None, - src: typing.Optional[CountMentionsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[MentionCountsResponse]: """ @@ -649,9 +629,6 @@ async def count( limit : typing.Optional[int] Rows in the ranked table, 1 to 40. Default 20. - src : typing.Optional[CountMentionsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -672,7 +649,6 @@ async def count( "published_after": published_after, "published_before": published_before, "limit": limit, - "src": src, }, request_options=request_options, ) diff --git a/src/arcmira/mentions/types/__init__.py b/src/arcmira/mentions/types/__init__.py index 611abaf..710d8c6 100644 --- a/src/arcmira/mentions/types/__init__.py +++ b/src/arcmira/mentions/types/__init__.py @@ -7,18 +7,14 @@ if typing.TYPE_CHECKING: from .count_mentions_request_mode import CountMentionsRequestMode - from .count_mentions_request_src import CountMentionsRequestSrc from .list_mentions_request_details import ListMentionsRequestDetails from .list_mentions_request_entity_type import ListMentionsRequestEntityType from .list_mentions_request_sentiment import ListMentionsRequestSentiment - from .list_mentions_request_src import ListMentionsRequestSrc _dynamic_imports: typing.Dict[str, str] = { "CountMentionsRequestMode": ".count_mentions_request_mode", - "CountMentionsRequestSrc": ".count_mentions_request_src", "ListMentionsRequestDetails": ".list_mentions_request_details", "ListMentionsRequestEntityType": ".list_mentions_request_entity_type", "ListMentionsRequestSentiment": ".list_mentions_request_sentiment", - "ListMentionsRequestSrc": ".list_mentions_request_src", } @@ -45,9 +41,7 @@ def __dir__(): __all__ = [ "CountMentionsRequestMode", - "CountMentionsRequestSrc", "ListMentionsRequestDetails", "ListMentionsRequestEntityType", "ListMentionsRequestSentiment", - "ListMentionsRequestSrc", ] diff --git a/src/arcmira/mentions/types/count_mentions_request_src.py b/src/arcmira/mentions/types/count_mentions_request_src.py deleted file mode 100644 index 88ccd73..0000000 --- a/src/arcmira/mentions/types/count_mentions_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CountMentionsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/mentions/types/list_mentions_request_src.py b/src/arcmira/mentions/types/list_mentions_request_src.py deleted file mode 100644 index 84409bd..0000000 --- a/src/arcmira/mentions/types/list_mentions_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListMentionsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/meta/__init__.py b/src/arcmira/meta/__init__.py deleted file mode 100644 index dadae70..0000000 --- a/src/arcmira/meta/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file diff --git a/src/arcmira/meta/client.py b/src/arcmira/meta/client.py deleted file mode 100644 index a55ebb9..0000000 --- a/src/arcmira/meta/client.py +++ /dev/null @@ -1,263 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from ..types.open_api_document import OpenApiDocument -from ..types.signup_sent_response import SignupSentResponse -from ..types.signup_verified_response import SignupVerifiedResponse -from .raw_client import AsyncRawMetaClient, RawMetaClient - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class MetaClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawMetaClient(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> RawMetaClient: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawMetaClient - """ - return self._raw_client - - def get_openapi_document(self, *, request_options: typing.Optional[RequestOptions] = None) -> OpenApiDocument: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - OpenApiDocument - This OpenAPI 3.1 document. - - Examples - -------- - from arcmira import Arcmira - - client = Arcmira( - api_key="YOUR_API_KEY", - ) - client.meta.get_openapi_document() - """ - _response = self._raw_client.get_openapi_document(request_options=request_options) - return _response.data - - def create_signup( - self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None - ) -> SignupSentResponse: - """ - Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. - - Parameters - ---------- - email : str - The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. - - src : typing.Optional[str] - The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SignupSentResponse - Code sent - - Examples - -------- - from arcmira import Arcmira - - client = Arcmira( - api_key="YOUR_API_KEY", - ) - client.meta.create_signup( - email="email", - ) - """ - _response = self._raw_client.create_signup(email=email, src=src, request_options=request_options) - return _response.data - - def verify_signup( - self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None - ) -> SignupVerifiedResponse: - """ - Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. - - Parameters - ---------- - email : str - The address the code was sent to. - - code : str - The six digit code from the email. Ten minutes, five attempts, then a new send is required. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SignupVerifiedResponse - Account key minted - - Examples - -------- - from arcmira import Arcmira - - client = Arcmira( - api_key="YOUR_API_KEY", - ) - client.meta.verify_signup( - email="email", - code="code", - ) - """ - _response = self._raw_client.verify_signup(email=email, code=code, request_options=request_options) - return _response.data - - -class AsyncMetaClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawMetaClient(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawMetaClient: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawMetaClient - """ - return self._raw_client - - async def get_openapi_document(self, *, request_options: typing.Optional[RequestOptions] = None) -> OpenApiDocument: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - OpenApiDocument - This OpenAPI 3.1 document. - - Examples - -------- - import asyncio - - from arcmira import AsyncArcmira - - client = AsyncArcmira( - api_key="YOUR_API_KEY", - ) - - - async def main() -> None: - await client.meta.get_openapi_document() - - - asyncio.run(main()) - """ - _response = await self._raw_client.get_openapi_document(request_options=request_options) - return _response.data - - async def create_signup( - self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None - ) -> SignupSentResponse: - """ - Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. - - Parameters - ---------- - email : str - The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. - - src : typing.Optional[str] - The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SignupSentResponse - Code sent - - Examples - -------- - import asyncio - - from arcmira import AsyncArcmira - - client = AsyncArcmira( - api_key="YOUR_API_KEY", - ) - - - async def main() -> None: - await client.meta.create_signup( - email="email", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.create_signup(email=email, src=src, request_options=request_options) - return _response.data - - async def verify_signup( - self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None - ) -> SignupVerifiedResponse: - """ - Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. - - Parameters - ---------- - email : str - The address the code was sent to. - - code : str - The six digit code from the email. Ten minutes, five attempts, then a new send is required. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SignupVerifiedResponse - Account key minted - - Examples - -------- - import asyncio - - from arcmira import AsyncArcmira - - client = AsyncArcmira( - api_key="YOUR_API_KEY", - ) - - - async def main() -> None: - await client.meta.verify_signup( - email="email", - code="code", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.verify_signup(email=email, code=code, request_options=request_options) - return _response.data diff --git a/src/arcmira/meta/raw_client.py b/src/arcmira/meta/raw_client.py deleted file mode 100644 index 06d6abb..0000000 --- a/src/arcmira/meta/raw_client.py +++ /dev/null @@ -1,612 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.http_response import AsyncHttpResponse, HttpResponse -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.internal_server_error import InternalServerError -from ..errors.not_found_error import NotFoundError -from ..errors.service_unavailable_error import ServiceUnavailableError -from ..errors.too_many_requests_error import TooManyRequestsError -from ..types.error import Error -from ..types.open_api_document import OpenApiDocument -from ..types.signup_sent_response import SignupSentResponse -from ..types.signup_verified_response import SignupVerifiedResponse -from pydantic import ValidationError - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class RawMetaClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get_openapi_document( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[OpenApiDocument]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[OpenApiDocument] - This OpenAPI 3.1 document. - """ - _response = self._client_wrapper.httpx_client.request( - "v1/openapi.json", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - OpenApiDocument, - parse_obj_as( - type_=OpenApiDocument, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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 create_signup( - self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[SignupSentResponse]: - """ - Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. - - Parameters - ---------- - email : str - The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. - - src : typing.Optional[str] - The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[SignupSentResponse] - Code sent - """ - _response = self._client_wrapper.httpx_client.request( - "v1/signups", - method="POST", - json={ - "email": email, - "src": src, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - SignupSentResponse, - parse_obj_as( - type_=SignupSentResponse, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 503: - raise ServiceUnavailableError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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 verify_signup( - self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[SignupVerifiedResponse]: - """ - Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. - - Parameters - ---------- - email : str - The address the code was sent to. - - code : str - The six digit code from the email. Ten minutes, five attempts, then a new send is required. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[SignupVerifiedResponse] - Account key minted - """ - _response = self._client_wrapper.httpx_client.request( - "v1/signups/verify", - method="POST", - json={ - "email": email, - "code": code, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - SignupVerifiedResponse, - parse_obj_as( - type_=SignupVerifiedResponse, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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) - - -class AsyncRawMetaClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get_openapi_document( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[OpenApiDocument]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[OpenApiDocument] - This OpenAPI 3.1 document. - """ - _response = await self._client_wrapper.httpx_client.request( - "v1/openapi.json", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - OpenApiDocument, - parse_obj_as( - type_=OpenApiDocument, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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 create_signup( - self, *, email: str, src: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[SignupSentResponse]: - """ - Starts the signup that ends in an account key, with no key and no login. Sends a 6 digit code to the address, valid for 10 minutes and 5 attempts, and answers 202 with the verify call. Sends are capped at 3 per address per hour, 10 per IP per hour, and 25 per client fingerprint per day; past a cap the response is 429 signup_send_limited with retry_after_seconds and an unlock whose action is this call. An address that already has an account gets a code too; verifying it mints a key on that account. Send { "email": "agent@example.com" }, with an optional "src" naming the surface that sent you. - - Parameters - ---------- - email : str - The address the verification code is sent to. Case is folded; the same address in any casing is one account and one send budget. - - src : typing.Optional[str] - The agent surface that sent you, as a value from the ?src= registry. Also accepted as ?src= on the URL. The unlock in a refusal carries it back. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[SignupSentResponse] - Code sent - """ - _response = await self._client_wrapper.httpx_client.request( - "v1/signups", - method="POST", - json={ - "email": email, - "src": src, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - SignupSentResponse, - parse_obj_as( - type_=SignupSentResponse, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 503: - raise ServiceUnavailableError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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 verify_signup( - self, *, email: str, code: str, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[SignupVerifiedResponse]: - """ - Consumes the code POST /v1/signups sent, creates the account when the address has none, and mints an arc_sk_ key on it: the read scope, the free tier's lifetime row pool, no expiry. A wrong, expired, or spent code is 400 signup_code_invalid on param code, naming the attempts left; its unlock action is a new send. Send { "email": "agent@example.com", "code": "482913" }. - - Parameters - ---------- - email : str - The address the code was sent to. - - code : str - The six digit code from the email. Ten minutes, five attempts, then a new send is required. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[SignupVerifiedResponse] - Account key minted - """ - _response = await self._client_wrapper.httpx_client.request( - "v1/signups/verify", - method="POST", - json={ - "email": email, - "code": code, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - SignupVerifiedResponse, - parse_obj_as( - type_=SignupVerifiedResponse, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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/arcmira/raw_client.py b/src/arcmira/raw_client.py deleted file mode 100644 index c79ed87..0000000 --- a/src/arcmira/raw_client.py +++ /dev/null @@ -1,294 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from .core.api_error import ApiError -from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.http_response import AsyncHttpResponse, HttpResponse -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.internal_server_error import InternalServerError -from .errors.not_found_error import NotFoundError -from .errors.payment_required_error import PaymentRequiredError -from .errors.too_many_requests_error import TooManyRequestsError -from .errors.unauthorized_error import UnauthorizedError -from .types.error import Error -from .types.search_request_type import SearchRequestType -from .types.search_resolve_response import SearchResolveResponse -from pydantic import ValidationError - - -class RawArcmira: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def search( - self, - *, - q: str, - type: typing.Optional[SearchRequestType] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[SearchResolveResponse]: - """ - Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. - - Parameters - ---------- - q : str - Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. - - type : typing.Optional[SearchRequestType] - Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[SearchResolveResponse] - Success - """ - _response = self._client_wrapper.httpx_client.request( - "v1/search", - method="GET", - params={ - "q": q, - "type": type, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - SearchResolveResponse, - parse_obj_as( - type_=SearchResolveResponse, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 402: - raise PaymentRequiredError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise ForbiddenError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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) - - -class AsyncRawArcmira: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def search( - self, - *, - q: str, - type: typing.Optional[SearchRequestType] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[SearchResolveResponse]: - """ - Single-result name resolver: exact, case-insensitive match with curated alias support. Returns at most one entity and does not paginate (single page; there is no cursor). When `type` is passed and the name resolves to an entity of a different type, the response is `{ found: false }`. For fuzzy multi-result discovery use /v1/entities/search instead. - - Parameters - ---------- - q : str - Entity name to resolve. Exact, case-insensitive match; curated merge-rule aliases (e.g. "Ford" resolving to Ford Motor Company) are honored. - - type : typing.Optional[SearchRequestType] - Restrict the match to one entity type. When the name resolves to an entity of a different type, the response is { found: false }. organization also matches legacy company/brand rows. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[SearchResolveResponse] - Success - """ - _response = await self._client_wrapper.httpx_client.request( - "v1/search", - method="GET", - params={ - "q": q, - "type": type, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - SearchResolveResponse, - parse_obj_as( - type_=SearchResolveResponse, # 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( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 402: - raise PaymentRequiredError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise ForbiddenError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 500: - raise InternalServerError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # 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/arcmira/recommendations/__init__.py b/src/arcmira/recommendations/__init__.py index 782a20a..539136d 100644 --- a/src/arcmira/recommendations/__init__.py +++ b/src/arcmira/recommendations/__init__.py @@ -6,15 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ( - ListRecommendationsRequestEntityType, - ListRecommendationsRequestMentionClass, - ListRecommendationsRequestSrc, - ) + from .types import ListRecommendationsRequestEntityType, ListRecommendationsRequestMentionClass _dynamic_imports: typing.Dict[str, str] = { "ListRecommendationsRequestEntityType": ".types", "ListRecommendationsRequestMentionClass": ".types", - "ListRecommendationsRequestSrc": ".types", } @@ -39,8 +34,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = [ - "ListRecommendationsRequestEntityType", - "ListRecommendationsRequestMentionClass", - "ListRecommendationsRequestSrc", -] +__all__ = ["ListRecommendationsRequestEntityType", "ListRecommendationsRequestMentionClass"] diff --git a/src/arcmira/recommendations/client.py b/src/arcmira/recommendations/client.py index 0aa41e6..c7bfd01 100644 --- a/src/arcmira/recommendations/client.py +++ b/src/arcmira/recommendations/client.py @@ -10,7 +10,6 @@ from .raw_client import AsyncRawRecommendationsClient, RawRecommendationsClient from .types.list_recommendations_request_entity_type import ListRecommendationsRequestEntityType from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass -from .types.list_recommendations_request_src import ListRecommendationsRequestSrc class RecommendationsClient: @@ -43,7 +42,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ @@ -76,9 +74,6 @@ def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -114,7 +109,6 @@ def list( date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) @@ -149,7 +143,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ @@ -182,9 +175,6 @@ async def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -229,6 +219,5 @@ async def main() -> None: date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) diff --git a/src/arcmira/recommendations/raw_client.py b/src/arcmira/recommendations/raw_client.py index 3255f97..2bf77c2 100644 --- a/src/arcmira/recommendations/raw_client.py +++ b/src/arcmira/recommendations/raw_client.py @@ -21,7 +21,6 @@ from ..types.recommendation_list_response import RecommendationListResponse from .types.list_recommendations_request_entity_type import ListRecommendationsRequestEntityType from .types.list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass -from .types.list_recommendations_request_src import ListRecommendationsRequestSrc from pydantic import ValidationError @@ -44,7 +43,6 @@ def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ @@ -77,9 +75,6 @@ def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -104,7 +99,6 @@ def list( "date_from": date_from, "date_to": date_to, "include_disputed": include_disputed, - "src": src, }, request_options=request_options, ) @@ -133,7 +127,6 @@ def list( date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) @@ -243,7 +236,6 @@ async def list( date_from: typing.Optional[str] = None, date_to: typing.Optional[str] = None, include_disputed: typing.Optional[bool] = None, - src: typing.Optional[ListRecommendationsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ @@ -276,9 +268,6 @@ async def list( include_disputed : typing.Optional[bool] - src : typing.Optional[ListRecommendationsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -303,7 +292,6 @@ async def list( "date_from": date_from, "date_to": date_to, "include_disputed": include_disputed, - "src": src, }, request_options=request_options, ) @@ -334,7 +322,6 @@ async def _get_next(): date_from=date_from, date_to=date_to, include_disputed=include_disputed, - src=src, request_options=request_options, ) diff --git a/src/arcmira/recommendations/types/__init__.py b/src/arcmira/recommendations/types/__init__.py index 5a56dd0..8585260 100644 --- a/src/arcmira/recommendations/types/__init__.py +++ b/src/arcmira/recommendations/types/__init__.py @@ -8,11 +8,9 @@ if typing.TYPE_CHECKING: from .list_recommendations_request_entity_type import ListRecommendationsRequestEntityType from .list_recommendations_request_mention_class import ListRecommendationsRequestMentionClass - from .list_recommendations_request_src import ListRecommendationsRequestSrc _dynamic_imports: typing.Dict[str, str] = { "ListRecommendationsRequestEntityType": ".list_recommendations_request_entity_type", "ListRecommendationsRequestMentionClass": ".list_recommendations_request_mention_class", - "ListRecommendationsRequestSrc": ".list_recommendations_request_src", } @@ -37,8 +35,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = [ - "ListRecommendationsRequestEntityType", - "ListRecommendationsRequestMentionClass", - "ListRecommendationsRequestSrc", -] +__all__ = ["ListRecommendationsRequestEntityType", "ListRecommendationsRequestMentionClass"] diff --git a/src/arcmira/recommendations/types/list_recommendations_request_src.py b/src/arcmira/recommendations/types/list_recommendations_request_src.py deleted file mode 100644 index a8ce855..0000000 --- a/src/arcmira/recommendations/types/list_recommendations_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListRecommendationsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/__init__.py b/src/arcmira/transcripts/__init__.py index 969eebe..cc669be 100644 --- a/src/arcmira/transcripts/__init__.py +++ b/src/arcmira/transcripts/__init__.py @@ -6,24 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ( - CaptionsTranscriptsRequestSrc, - GetTranscriptsRequestQuality, - GetTranscriptsRequestSrc, - ListRequestsTranscriptsRequestSrc, - SearchTranscriptsRequestSource, - SearchTranscriptsRequestSrc, - StatusTranscriptsRequestSrc, - ) + from .types import GetTranscriptsRequestQuality, SearchTranscriptsRequestSource from . import edits, merges, speakers _dynamic_imports: typing.Dict[str, str] = { - "CaptionsTranscriptsRequestSrc": ".types", "GetTranscriptsRequestQuality": ".types", - "GetTranscriptsRequestSrc": ".types", - "ListRequestsTranscriptsRequestSrc": ".types", "SearchTranscriptsRequestSource": ".types", - "SearchTranscriptsRequestSrc": ".types", - "StatusTranscriptsRequestSrc": ".types", "edits": ".edits", "merges": ".merges", "speakers": ".speakers", @@ -51,15 +38,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = [ - "CaptionsTranscriptsRequestSrc", - "GetTranscriptsRequestQuality", - "GetTranscriptsRequestSrc", - "ListRequestsTranscriptsRequestSrc", - "SearchTranscriptsRequestSource", - "SearchTranscriptsRequestSrc", - "StatusTranscriptsRequestSrc", - "edits", - "merges", - "speakers", -] +__all__ = ["GetTranscriptsRequestQuality", "SearchTranscriptsRequestSource", "edits", "merges", "speakers"] diff --git a/src/arcmira/transcripts/client.py b/src/arcmira/transcripts/client.py index 69bdc73..9bec4be 100644 --- a/src/arcmira/transcripts/client.py +++ b/src/arcmira/transcripts/client.py @@ -8,21 +8,16 @@ from ..core.pagination import AsyncPager, SyncPager from ..core.request_options import RequestOptions from ..types.transcript_purchase_quote import TranscriptPurchaseQuote +from ..types.transcript_request import TranscriptRequest +from ..types.transcript_request_list_response import TranscriptRequestListResponse +from ..types.transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem +from ..types.transcript_request_submit_response import TranscriptRequestSubmitResponse from ..types.transcript_result import TranscriptResult from ..types.transcript_search_response import TranscriptSearchResponse -from ..types.transcription_list_response import TranscriptionListResponse -from ..types.transcription_list_response_requests_item import TranscriptionListResponseRequestsItem -from ..types.transcription_request import TranscriptionRequest -from ..types.transcription_submit_response import TranscriptionSubmitResponse from ..types.video_captions_response import VideoCaptionsResponse from .raw_client import AsyncRawTranscriptsClient, RawTranscriptsClient -from .types.captions_transcripts_request_src import CaptionsTranscriptsRequestSrc from .types.get_transcripts_request_quality import GetTranscriptsRequestQuality -from .types.get_transcripts_request_src import GetTranscriptsRequestSrc -from .types.list_requests_transcripts_request_src import ListRequestsTranscriptsRequestSrc from .types.search_transcripts_request_source import SearchTranscriptsRequestSource -from .types.search_transcripts_request_src import SearchTranscriptsRequestSrc -from .types.status_transcripts_request_src import StatusTranscriptsRequestSrc if typing.TYPE_CHECKING: from .edits.client import AsyncEditsClient, EditsClient @@ -65,7 +60,6 @@ def search( published_before: typing.Optional[str] = None, source: typing.Optional[SearchTranscriptsRequestSource] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptSearchResponse: """ @@ -106,9 +100,6 @@ def search( limit : typing.Optional[int] Chunks to return, 1 to 20. Default 5. - src : typing.Optional[SearchTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -140,7 +131,6 @@ def search( published_before=published_before, source=source, limit=limit, - src=src, request_options=request_options, ) return _response.data @@ -155,7 +145,6 @@ def get( start: typing.Optional[float] = None, end: typing.Optional[float] = None, refresh: typing.Optional[bool] = None, - src: typing.Optional[GetTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptResult: """ @@ -184,9 +173,6 @@ def get( refresh : typing.Optional[bool] Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. - src : typing.Optional[GetTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -214,7 +200,6 @@ def get( start=start, end=end, refresh=refresh, - src=src, request_options=request_options, ) return _response.data @@ -253,11 +238,7 @@ def quote( return _response.data def captions( - self, - video_id: str, - *, - src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> VideoCaptionsResponse: """ Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. @@ -267,9 +248,6 @@ def captions( video_id : str YouTube video id, 11 characters. - src : typing.Optional[CaptionsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -289,7 +267,7 @@ def captions( video_id="video_id", ) """ - _response = self._raw_client.captions(video_id, src=src, request_options=request_options) + _response = self._raw_client.captions(video_id, request_options=request_options) return _response.data def list_requests( @@ -298,9 +276,8 @@ def list_requests( video_id: typing.Optional[str] = None, limit: typing.Optional[int] = None, cursor: typing.Optional[str] = None, - src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + ) -> SyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. @@ -315,15 +292,12 @@ def list_requests( cursor : typing.Optional[str] Signed continuation from next_cursor. Keep the same filter, limit and credential. - src : typing.Optional[ListRequestsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + SyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse] Success Examples @@ -341,7 +315,7 @@ def list_requests( yield page """ return self._raw_client.list_requests( - video_id=video_id, limit=limit, cursor=cursor, src=src, request_options=request_options + video_id=video_id, limit=limit, cursor=cursor, request_options=request_options ) def request( @@ -353,7 +327,7 @@ def request( video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> TranscriptionSubmitResponse: + ) -> TranscriptRequestSubmitResponse: """ Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. @@ -379,7 +353,7 @@ def request( Returns ------- - TranscriptionSubmitResponse + TranscriptRequestSubmitResponse An existing in-flight or already-satisfied request was returned (existing: true) Examples @@ -404,13 +378,7 @@ def request( ) return _response.data - def status( - self, - id: str, - *, - src: typing.Optional[StatusTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> TranscriptionRequest: + def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> TranscriptRequest: """ Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. @@ -419,15 +387,12 @@ def status( id : str Transcription request id, the UUID POST /v1/transcriptions returned. - src : typing.Optional[StatusTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - TranscriptionRequest + TranscriptRequest Success Examples @@ -441,7 +406,7 @@ def status( id="id", ) """ - _response = self._raw_client.status(id, src=src, request_options=request_options) + _response = self._raw_client.status(id, request_options=request_options) return _response.data @property @@ -502,7 +467,6 @@ async def search( published_before: typing.Optional[str] = None, source: typing.Optional[SearchTranscriptsRequestSource] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptSearchResponse: """ @@ -543,9 +507,6 @@ async def search( limit : typing.Optional[int] Chunks to return, 1 to 20. Default 5. - src : typing.Optional[SearchTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -585,7 +546,6 @@ async def main() -> None: published_before=published_before, source=source, limit=limit, - src=src, request_options=request_options, ) return _response.data @@ -600,7 +560,6 @@ async def get( start: typing.Optional[float] = None, end: typing.Optional[float] = None, refresh: typing.Optional[bool] = None, - src: typing.Optional[GetTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptResult: """ @@ -629,9 +588,6 @@ async def get( refresh : typing.Optional[bool] Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. - src : typing.Optional[GetTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -667,7 +623,6 @@ async def main() -> None: start=start, end=end, refresh=refresh, - src=src, request_options=request_options, ) return _response.data @@ -714,11 +669,7 @@ async def main() -> None: return _response.data async def captions( - self, - video_id: str, - *, - src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> VideoCaptionsResponse: """ Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. @@ -728,9 +679,6 @@ async def captions( video_id : str YouTube video id, 11 characters. - src : typing.Optional[CaptionsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -758,7 +706,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.captions(video_id, src=src, request_options=request_options) + _response = await self._raw_client.captions(video_id, request_options=request_options) return _response.data async def list_requests( @@ -767,9 +715,8 @@ async def list_requests( video_id: typing.Optional[str] = None, limit: typing.Optional[int] = None, cursor: typing.Optional[str] = None, - src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + ) -> AsyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. @@ -784,15 +731,12 @@ async def list_requests( cursor : typing.Optional[str] Signed continuation from next_cursor. Keep the same filter, limit and credential. - src : typing.Optional[ListRequestsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + AsyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse] Success Examples @@ -819,7 +763,7 @@ async def main() -> None: asyncio.run(main()) """ return await self._raw_client.list_requests( - video_id=video_id, limit=limit, cursor=cursor, src=src, request_options=request_options + video_id=video_id, limit=limit, cursor=cursor, request_options=request_options ) async def request( @@ -831,7 +775,7 @@ async def request( video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> TranscriptionSubmitResponse: + ) -> TranscriptRequestSubmitResponse: """ Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. @@ -857,7 +801,7 @@ async def request( Returns ------- - TranscriptionSubmitResponse + TranscriptRequestSubmitResponse An existing in-flight or already-satisfied request was returned (existing: true) Examples @@ -890,13 +834,7 @@ async def main() -> None: ) return _response.data - async def status( - self, - id: str, - *, - src: typing.Optional[StatusTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> TranscriptionRequest: + async def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> TranscriptRequest: """ Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. @@ -905,15 +843,12 @@ async def status( id : str Transcription request id, the UUID POST /v1/transcriptions returned. - src : typing.Optional[StatusTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - TranscriptionRequest + TranscriptRequest Success Examples @@ -935,7 +870,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.status(id, src=src, request_options=request_options) + _response = await self._raw_client.status(id, request_options=request_options) return _response.data @property diff --git a/src/arcmira/transcripts/raw_client.py b/src/arcmira/transcripts/raw_client.py index 41964e2..84f2c05 100644 --- a/src/arcmira/transcripts/raw_client.py +++ b/src/arcmira/transcripts/raw_client.py @@ -23,20 +23,15 @@ from ..errors.unprocessable_entity_error import UnprocessableEntityError from ..types.error import Error from ..types.transcript_purchase_quote import TranscriptPurchaseQuote +from ..types.transcript_request import TranscriptRequest +from ..types.transcript_request_list_response import TranscriptRequestListResponse +from ..types.transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem +from ..types.transcript_request_submit_response import TranscriptRequestSubmitResponse from ..types.transcript_result import TranscriptResult from ..types.transcript_search_response import TranscriptSearchResponse -from ..types.transcription_list_response import TranscriptionListResponse -from ..types.transcription_list_response_requests_item import TranscriptionListResponseRequestsItem -from ..types.transcription_request import TranscriptionRequest -from ..types.transcription_submit_response import TranscriptionSubmitResponse from ..types.video_captions_response import VideoCaptionsResponse -from .types.captions_transcripts_request_src import CaptionsTranscriptsRequestSrc from .types.get_transcripts_request_quality import GetTranscriptsRequestQuality -from .types.get_transcripts_request_src import GetTranscriptsRequestSrc -from .types.list_requests_transcripts_request_src import ListRequestsTranscriptsRequestSrc from .types.search_transcripts_request_source import SearchTranscriptsRequestSource -from .types.search_transcripts_request_src import SearchTranscriptsRequestSrc -from .types.status_transcripts_request_src import StatusTranscriptsRequestSrc from pydantic import ValidationError # this is used as the default value for optional parameters @@ -61,7 +56,6 @@ def search( published_before: typing.Optional[str] = None, source: typing.Optional[SearchTranscriptsRequestSource] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptSearchResponse]: """ @@ -102,9 +96,6 @@ def search( limit : typing.Optional[int] Chunks to return, 1 to 20. Default 5. - src : typing.Optional[SearchTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -128,7 +119,6 @@ def search( "published_before": published_before, "source": source, "limit": limit, - "src": src, }, request_options=request_options, ) @@ -249,7 +239,6 @@ def get( start: typing.Optional[float] = None, end: typing.Optional[float] = None, refresh: typing.Optional[bool] = None, - src: typing.Optional[GetTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptResult]: """ @@ -278,9 +267,6 @@ def get( refresh : typing.Optional[bool] Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. - src : typing.Optional[GetTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -299,7 +285,6 @@ def get( "start": start, "end": end, "refresh": refresh, - "src": src, }, request_options=request_options, ) @@ -520,11 +505,7 @@ def quote( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def captions( - self, - video_id: str, - *, - src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[VideoCaptionsResponse]: """ Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. @@ -534,9 +515,6 @@ def captions( video_id : str YouTube video id, 11 characters. - src : typing.Optional[CaptionsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -548,9 +526,6 @@ def captions( _response = self._client_wrapper.httpx_client.request( f"v1/videos/{encode_path_param(video_id)}/captions", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: @@ -655,9 +630,8 @@ def list_requests( video_id: typing.Optional[str] = None, limit: typing.Optional[int] = None, cursor: typing.Optional[str] = None, - src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + ) -> SyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. @@ -672,15 +646,12 @@ def list_requests( cursor : typing.Optional[str] Signed continuation from next_cursor. Keep the same filter, limit and credential. - src : typing.Optional[ListRequestsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - SyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + SyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse] Success """ _response = self._client_wrapper.httpx_client.request( @@ -690,16 +661,15 @@ def list_requests( "video_id": video_id, "limit": limit, "cursor": cursor, - "src": src, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _parsed_response = typing.cast( - TranscriptionListResponse, + TranscriptRequestListResponse, parse_obj_as( - type_=TranscriptionListResponse, # type: ignore + type_=TranscriptRequestListResponse, # type: ignore object_=_response.json(), ), ) @@ -710,7 +680,6 @@ def list_requests( video_id=video_id, limit=limit, cursor=_parsed_next, - src=src, request_options=request_options, ) return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) @@ -798,7 +767,7 @@ def request( video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[TranscriptionSubmitResponse]: + ) -> HttpResponse[TranscriptRequestSubmitResponse]: """ Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. @@ -824,7 +793,7 @@ def request( Returns ------- - HttpResponse[TranscriptionSubmitResponse] + HttpResponse[TranscriptRequestSubmitResponse] An existing in-flight or already-satisfied request was returned (existing: true) """ _response = self._client_wrapper.httpx_client.request( @@ -846,9 +815,9 @@ def request( try: if 200 <= _response.status_code < 300: _data = typing.cast( - TranscriptionSubmitResponse, + TranscriptRequestSubmitResponse, parse_obj_as( - type_=TranscriptionSubmitResponse, # type: ignore + type_=TranscriptRequestSubmitResponse, # type: ignore object_=_response.json(), ), ) @@ -962,12 +931,8 @@ def request( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def status( - self, - id: str, - *, - src: typing.Optional[StatusTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[TranscriptionRequest]: + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TranscriptRequest]: """ Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. @@ -976,31 +941,25 @@ def status( id : str Transcription request id, the UUID POST /v1/transcriptions returned. - src : typing.Optional[StatusTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[TranscriptionRequest] + HttpResponse[TranscriptRequest] Success """ _response = self._client_wrapper.httpx_client.request( f"v1/transcriptions/{encode_path_param(id)}", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TranscriptionRequest, + TranscriptRequest, parse_obj_as( - type_=TranscriptionRequest, # type: ignore + type_=TranscriptRequest, # type: ignore object_=_response.json(), ), ) @@ -1099,7 +1058,6 @@ async def search( published_before: typing.Optional[str] = None, source: typing.Optional[SearchTranscriptsRequestSource] = None, limit: typing.Optional[int] = None, - src: typing.Optional[SearchTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptSearchResponse]: """ @@ -1140,9 +1098,6 @@ async def search( limit : typing.Optional[int] Chunks to return, 1 to 20. Default 5. - src : typing.Optional[SearchTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1166,7 +1121,6 @@ async def search( "published_before": published_before, "source": source, "limit": limit, - "src": src, }, request_options=request_options, ) @@ -1287,7 +1241,6 @@ async def get( start: typing.Optional[float] = None, end: typing.Optional[float] = None, refresh: typing.Optional[bool] = None, - src: typing.Optional[GetTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptResult]: """ @@ -1316,9 +1269,6 @@ async def get( refresh : typing.Optional[bool] Captions only; Premium with refresh=true returns invalid_query. Refetch the caption track from YouTube instead of serving the stored copy. Available only for videos outside our index; a pipeline-owned video refuses it with invalid_query. - src : typing.Optional[GetTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1337,7 +1287,6 @@ async def get( "start": start, "end": end, "refresh": refresh, - "src": src, }, request_options=request_options, ) @@ -1558,11 +1507,7 @@ async def quote( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def captions( - self, - video_id: str, - *, - src: typing.Optional[CaptionsTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, + self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[VideoCaptionsResponse]: """ Free (0 rows), any key. Returns the video metadata and every caption track YouTube lists for it, each as { code, name, generated }. Call it when GET /v1/transcripts/{video_id} answered transcript_unavailable without languages, or before asking for a specific track. Listing is served from a day-long cache; a cold listing answers 503 transcript_fetching with Retry-After while the fetch continues in the background. @@ -1572,9 +1517,6 @@ async def captions( video_id : str YouTube video id, 11 characters. - src : typing.Optional[CaptionsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1586,9 +1528,6 @@ async def captions( _response = await self._client_wrapper.httpx_client.request( f"v1/videos/{encode_path_param(video_id)}/captions", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: @@ -1693,9 +1632,8 @@ async def list_requests( video_id: typing.Optional[str] = None, limit: typing.Optional[int] = None, cursor: typing.Optional[str] = None, - src: typing.Optional[ListRequestsTranscriptsRequestSrc] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse]: + ) -> AsyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. @@ -1710,15 +1648,12 @@ async def list_requests( cursor : typing.Optional[str] Signed continuation from next_cursor. Keep the same filter, limit and credential. - src : typing.Optional[ListRequestsTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncPager[TranscriptionListResponseRequestsItem, TranscriptionListResponse] + AsyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse] Success """ _response = await self._client_wrapper.httpx_client.request( @@ -1728,16 +1663,15 @@ async def list_requests( "video_id": video_id, "limit": limit, "cursor": cursor, - "src": src, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _parsed_response = typing.cast( - TranscriptionListResponse, + TranscriptRequestListResponse, parse_obj_as( - type_=TranscriptionListResponse, # type: ignore + type_=TranscriptRequestListResponse, # type: ignore object_=_response.json(), ), ) @@ -1750,7 +1684,6 @@ async def _get_next(): video_id=video_id, limit=limit, cursor=_parsed_next, - src=src, request_options=request_options, ) @@ -1839,7 +1772,7 @@ async def request( video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TranscriptionSubmitResponse]: + ) -> AsyncHttpResponse[TranscriptRequestSubmitResponse]: """ Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. @@ -1865,7 +1798,7 @@ async def request( Returns ------- - AsyncHttpResponse[TranscriptionSubmitResponse] + AsyncHttpResponse[TranscriptRequestSubmitResponse] An existing in-flight or already-satisfied request was returned (existing: true) """ _response = await self._client_wrapper.httpx_client.request( @@ -1887,9 +1820,9 @@ async def request( try: if 200 <= _response.status_code < 300: _data = typing.cast( - TranscriptionSubmitResponse, + TranscriptRequestSubmitResponse, parse_obj_as( - type_=TranscriptionSubmitResponse, # type: ignore + type_=TranscriptRequestSubmitResponse, # type: ignore object_=_response.json(), ), ) @@ -2003,12 +1936,8 @@ async def request( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def status( - self, - id: str, - *, - src: typing.Optional[StatusTranscriptsRequestSrc] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TranscriptionRequest]: + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TranscriptRequest]: """ Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. @@ -2017,31 +1946,25 @@ async def status( id : str Transcription request id, the UUID POST /v1/transcriptions returned. - src : typing.Optional[StatusTranscriptsRequestSrc] - The surface making this call. The Arcmira MCP server sends mcp-tool so every unlock link in a gate attributes to the directory install. Omit from your own client. - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TranscriptionRequest] + AsyncHttpResponse[TranscriptRequest] Success """ _response = await self._client_wrapper.httpx_client.request( f"v1/transcriptions/{encode_path_param(id)}", method="GET", - params={ - "src": src, - }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TranscriptionRequest, + TranscriptRequest, parse_obj_as( - type_=TranscriptionRequest, # type: ignore + type_=TranscriptRequest, # type: ignore object_=_response.json(), ), ) diff --git a/src/arcmira/transcripts/types/__init__.py b/src/arcmira/transcripts/types/__init__.py index ccf092c..66f6543 100644 --- a/src/arcmira/transcripts/types/__init__.py +++ b/src/arcmira/transcripts/types/__init__.py @@ -6,21 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .captions_transcripts_request_src import CaptionsTranscriptsRequestSrc from .get_transcripts_request_quality import GetTranscriptsRequestQuality - from .get_transcripts_request_src import GetTranscriptsRequestSrc - from .list_requests_transcripts_request_src import ListRequestsTranscriptsRequestSrc from .search_transcripts_request_source import SearchTranscriptsRequestSource - from .search_transcripts_request_src import SearchTranscriptsRequestSrc - from .status_transcripts_request_src import StatusTranscriptsRequestSrc _dynamic_imports: typing.Dict[str, str] = { - "CaptionsTranscriptsRequestSrc": ".captions_transcripts_request_src", "GetTranscriptsRequestQuality": ".get_transcripts_request_quality", - "GetTranscriptsRequestSrc": ".get_transcripts_request_src", - "ListRequestsTranscriptsRequestSrc": ".list_requests_transcripts_request_src", "SearchTranscriptsRequestSource": ".search_transcripts_request_source", - "SearchTranscriptsRequestSrc": ".search_transcripts_request_src", - "StatusTranscriptsRequestSrc": ".status_transcripts_request_src", } @@ -45,12 +35,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = [ - "CaptionsTranscriptsRequestSrc", - "GetTranscriptsRequestQuality", - "GetTranscriptsRequestSrc", - "ListRequestsTranscriptsRequestSrc", - "SearchTranscriptsRequestSource", - "SearchTranscriptsRequestSrc", - "StatusTranscriptsRequestSrc", -] +__all__ = ["GetTranscriptsRequestQuality", "SearchTranscriptsRequestSource"] diff --git a/src/arcmira/transcripts/types/captions_transcripts_request_src.py b/src/arcmira/transcripts/types/captions_transcripts_request_src.py deleted file mode 100644 index a7d335e..0000000 --- a/src/arcmira/transcripts/types/captions_transcripts_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CaptionsTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/get_transcripts_request_src.py b/src/arcmira/transcripts/types/get_transcripts_request_src.py deleted file mode 100644 index ff73274..0000000 --- a/src/arcmira/transcripts/types/get_transcripts_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -GetTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/list_requests_transcripts_request_src.py b/src/arcmira/transcripts/types/list_requests_transcripts_request_src.py deleted file mode 100644 index c8e2587..0000000 --- a/src/arcmira/transcripts/types/list_requests_transcripts_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListRequestsTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/search_transcripts_request_src.py b/src/arcmira/transcripts/types/search_transcripts_request_src.py deleted file mode 100644 index c998c06..0000000 --- a/src/arcmira/transcripts/types/search_transcripts_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -SearchTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/transcripts/types/status_transcripts_request_src.py b/src/arcmira/transcripts/types/status_transcripts_request_src.py deleted file mode 100644 index 92591cc..0000000 --- a/src/arcmira/transcripts/types/status_transcripts_request_src.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -StatusTranscriptsRequestSrc = typing.Union[typing.Literal["mcp-tool"], typing.Any] diff --git a/src/arcmira/types/__init__.py b/src/arcmira/types/__init__.py index 92f2fb0..0ca8462 100644 --- a/src/arcmira/types/__init__.py +++ b/src/arcmira/types/__init__.py @@ -302,7 +302,6 @@ from .resolve_suggestion import ResolveSuggestion from .resolve_suggestion_match import ResolveSuggestionMatch from .resolve_suggestion_reason import ResolveSuggestionReason - from .search_request_type import SearchRequestType from .search_resolve_response import SearchResolveResponse from .search_resolve_response_entity import SearchResolveResponseEntity from .signup_sent_response import SignupSentResponse @@ -361,6 +360,16 @@ from .transcript_purchase_quote_charge import TranscriptPurchaseQuoteCharge from .transcript_purchase_quote_charge_unit import TranscriptPurchaseQuoteChargeUnit from .transcript_quote import TranscriptQuote + from .transcript_request import TranscriptRequest + from .transcript_request_charge import TranscriptRequestCharge + from .transcript_request_charge_unit import TranscriptRequestChargeUnit + from .transcript_request_list_response import TranscriptRequestListResponse + from .transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem + from .transcript_request_quote import TranscriptRequestQuote + from .transcript_request_stage import TranscriptRequestStage + from .transcript_request_state import TranscriptRequestState + from .transcript_request_status import TranscriptRequestStatus + from .transcript_request_submit_response import TranscriptRequestSubmitResponse from .transcript_response import TranscriptResponse from .transcript_response_access import TranscriptResponseAccess from .transcript_response_access_gate import TranscriptResponseAccessGate @@ -390,16 +399,6 @@ from .transcript_settings import TranscriptSettings from .transcript_settings_quality import TranscriptSettingsQuality from .transcript_video import TranscriptVideo - from .transcription_list_response import TranscriptionListResponse - from .transcription_list_response_requests_item import TranscriptionListResponseRequestsItem - from .transcription_request import TranscriptionRequest - from .transcription_request_charge import TranscriptionRequestCharge - from .transcription_request_charge_unit import TranscriptionRequestChargeUnit - from .transcription_request_quote import TranscriptionRequestQuote - from .transcription_request_stage import TranscriptionRequestStage - from .transcription_request_state import TranscriptionRequestState - from .transcription_request_status import TranscriptionRequestStatus - from .transcription_submit_response import TranscriptionSubmitResponse from .video_captions_response import VideoCaptionsResponse from .video_merge_list_response import VideoMergeListResponse from .video_merge_list_response_merges_item import VideoMergeListResponseMergesItem @@ -705,7 +704,6 @@ "ResolveSuggestion": ".resolve_suggestion", "ResolveSuggestionMatch": ".resolve_suggestion_match", "ResolveSuggestionReason": ".resolve_suggestion_reason", - "SearchRequestType": ".search_request_type", "SearchResolveResponse": ".search_resolve_response", "SearchResolveResponseEntity": ".search_resolve_response_entity", "SignupSentResponse": ".signup_sent_response", @@ -758,6 +756,16 @@ "TranscriptPurchaseQuoteCharge": ".transcript_purchase_quote_charge", "TranscriptPurchaseQuoteChargeUnit": ".transcript_purchase_quote_charge_unit", "TranscriptQuote": ".transcript_quote", + "TranscriptRequest": ".transcript_request", + "TranscriptRequestCharge": ".transcript_request_charge", + "TranscriptRequestChargeUnit": ".transcript_request_charge_unit", + "TranscriptRequestListResponse": ".transcript_request_list_response", + "TranscriptRequestListResponseRequestsItem": ".transcript_request_list_response_requests_item", + "TranscriptRequestQuote": ".transcript_request_quote", + "TranscriptRequestStage": ".transcript_request_stage", + "TranscriptRequestState": ".transcript_request_state", + "TranscriptRequestStatus": ".transcript_request_status", + "TranscriptRequestSubmitResponse": ".transcript_request_submit_response", "TranscriptResponse": ".transcript_response", "TranscriptResponseAccess": ".transcript_response_access", "TranscriptResponseAccessGate": ".transcript_response_access_gate", @@ -789,16 +797,6 @@ "TranscriptSettings": ".transcript_settings", "TranscriptSettingsQuality": ".transcript_settings_quality", "TranscriptVideo": ".transcript_video", - "TranscriptionListResponse": ".transcription_list_response", - "TranscriptionListResponseRequestsItem": ".transcription_list_response_requests_item", - "TranscriptionRequest": ".transcription_request", - "TranscriptionRequestCharge": ".transcription_request_charge", - "TranscriptionRequestChargeUnit": ".transcription_request_charge_unit", - "TranscriptionRequestQuote": ".transcription_request_quote", - "TranscriptionRequestStage": ".transcription_request_stage", - "TranscriptionRequestState": ".transcription_request_state", - "TranscriptionRequestStatus": ".transcription_request_status", - "TranscriptionSubmitResponse": ".transcription_submit_response", "VideoCaptionsResponse": ".video_captions_response", "VideoMergeListResponse": ".video_merge_list_response", "VideoMergeListResponseMergesItem": ".video_merge_list_response_merges_item", @@ -1128,7 +1126,6 @@ def __dir__(): "ResolveSuggestion", "ResolveSuggestionMatch", "ResolveSuggestionReason", - "SearchRequestType", "SearchResolveResponse", "SearchResolveResponseEntity", "SignupSentResponse", @@ -1181,6 +1178,16 @@ def __dir__(): "TranscriptPurchaseQuoteCharge", "TranscriptPurchaseQuoteChargeUnit", "TranscriptQuote", + "TranscriptRequest", + "TranscriptRequestCharge", + "TranscriptRequestChargeUnit", + "TranscriptRequestListResponse", + "TranscriptRequestListResponseRequestsItem", + "TranscriptRequestQuote", + "TranscriptRequestStage", + "TranscriptRequestState", + "TranscriptRequestStatus", + "TranscriptRequestSubmitResponse", "TranscriptResponse", "TranscriptResponseAccess", "TranscriptResponseAccessGate", @@ -1212,16 +1219,6 @@ def __dir__(): "TranscriptSettings", "TranscriptSettingsQuality", "TranscriptVideo", - "TranscriptionListResponse", - "TranscriptionListResponseRequestsItem", - "TranscriptionRequest", - "TranscriptionRequestCharge", - "TranscriptionRequestChargeUnit", - "TranscriptionRequestQuote", - "TranscriptionRequestStage", - "TranscriptionRequestState", - "TranscriptionRequestStatus", - "TranscriptionSubmitResponse", "VideoCaptionsResponse", "VideoMergeListResponse", "VideoMergeListResponseMergesItem", diff --git a/src/arcmira/types/search_request_type.py b/src/arcmira/types/search_request_type.py deleted file mode 100644 index c31bdbb..0000000 --- a/src/arcmira/types/search_request_type.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -SearchRequestType = typing.Union[typing.Literal["person", "organization", "product", "topic", "channel"], typing.Any] diff --git a/src/arcmira/types/transcription_request.py b/src/arcmira/types/transcript_request.py similarity index 84% rename from src/arcmira/types/transcription_request.py rename to src/arcmira/types/transcript_request.py index a8beabe..9166eb8 100644 --- a/src/arcmira/types/transcription_request.py +++ b/src/arcmira/types/transcript_request.py @@ -6,14 +6,14 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata -from .transcription_request_charge import TranscriptionRequestCharge -from .transcription_request_quote import TranscriptionRequestQuote -from .transcription_request_stage import TranscriptionRequestStage -from .transcription_request_state import TranscriptionRequestState -from .transcription_request_status import TranscriptionRequestStatus +from .transcript_request_charge import TranscriptRequestCharge +from .transcript_request_quote import TranscriptRequestQuote +from .transcript_request_stage import TranscriptRequestStage +from .transcript_request_state import TranscriptRequestState +from .transcript_request_status import TranscriptRequestStatus -class TranscriptionRequest(UniversalBaseModel): +class TranscriptRequest(UniversalBaseModel): id: typing.Optional[str] = pydantic.Field(default=None) """ Transcription request id (UUID). Null only in the degenerate submit response for a video you already own that has no request history. @@ -28,23 +28,23 @@ class TranscriptionRequest(UniversalBaseModel): YouTube video id (11 characters). """ - status: TranscriptionRequestStatus = pydantic.Field() + status: TranscriptRequestStatus = pydantic.Field() """ Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked). """ - state: TranscriptionRequestState - charge: typing.Optional[TranscriptionRequestCharge] = pydantic.Field(default=None) + state: TranscriptRequestState + charge: typing.Optional[TranscriptRequestCharge] = pydantic.Field(default=None) """ Accepted charge units. Present on durable purchases; absent only on legacy requests. """ - stage: typing.Optional[TranscriptionRequestStage] = pydantic.Field(default=None) + stage: typing.Optional[TranscriptRequestStage] = pydantic.Field(default=None) """ User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses. """ - quote: TranscriptionRequestQuote = pydantic.Field() + quote: TranscriptRequestQuote = pydantic.Field() """ What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free. """ diff --git a/src/arcmira/types/transcription_request_charge.py b/src/arcmira/types/transcript_request_charge.py similarity index 78% rename from src/arcmira/types/transcription_request_charge.py rename to src/arcmira/types/transcript_request_charge.py index 291966a..d0c9474 100644 --- a/src/arcmira/types/transcription_request_charge.py +++ b/src/arcmira/types/transcript_request_charge.py @@ -4,15 +4,15 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .transcription_request_charge_unit import TranscriptionRequestChargeUnit +from .transcript_request_charge_unit import TranscriptRequestChargeUnit -class TranscriptionRequestCharge(UniversalBaseModel): +class TranscriptRequestCharge(UniversalBaseModel): """ Accepted charge units. Present on durable purchases; absent only on legacy requests. """ - unit: TranscriptionRequestChargeUnit + unit: TranscriptRequestChargeUnit amount: float credits_per_row: float diff --git a/src/arcmira/types/transcript_request_charge_unit.py b/src/arcmira/types/transcript_request_charge_unit.py new file mode 100644 index 0000000..8fd41ff --- /dev/null +++ b/src/arcmira/types/transcript_request_charge_unit.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptRequestChargeUnit = typing.Union[typing.Literal["rows", "credits"], typing.Any] diff --git a/src/arcmira/types/transcription_list_response.py b/src/arcmira/types/transcript_request_list_response.py similarity index 77% rename from src/arcmira/types/transcription_list_response.py rename to src/arcmira/types/transcript_request_list_response.py index 14e650a..d542ca9 100644 --- a/src/arcmira/types/transcription_list_response.py +++ b/src/arcmira/types/transcript_request_list_response.py @@ -4,11 +4,11 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .transcription_list_response_requests_item import TranscriptionListResponseRequestsItem +from .transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem -class TranscriptionListResponse(UniversalBaseModel): - requests: typing.List[TranscriptionListResponseRequestsItem] = pydantic.Field() +class TranscriptRequestListResponse(UniversalBaseModel): + requests: typing.List[TranscriptRequestListResponseRequestsItem] = pydantic.Field() """ Your requests in descending creation time and id order, up to the requested limit. """ diff --git a/src/arcmira/types/transcription_list_response_requests_item.py b/src/arcmira/types/transcript_request_list_response_requests_item.py similarity index 82% rename from src/arcmira/types/transcription_list_response_requests_item.py rename to src/arcmira/types/transcript_request_list_response_requests_item.py index 81b3b7d..8ff1494 100644 --- a/src/arcmira/types/transcription_list_response_requests_item.py +++ b/src/arcmira/types/transcript_request_list_response_requests_item.py @@ -4,10 +4,10 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2 -from .transcription_request import TranscriptionRequest +from .transcript_request import TranscriptRequest -class TranscriptionListResponseRequestsItem(TranscriptionRequest): +class TranscriptRequestListResponseRequestsItem(TranscriptRequest): title: typing.Optional[str] = pydantic.Field(default=None) """ Video title for display. Null when unknown. diff --git a/src/arcmira/types/transcription_request_quote.py b/src/arcmira/types/transcript_request_quote.py similarity index 93% rename from src/arcmira/types/transcription_request_quote.py rename to src/arcmira/types/transcript_request_quote.py index bc3ad53..439c346 100644 --- a/src/arcmira/types/transcription_request_quote.py +++ b/src/arcmira/types/transcript_request_quote.py @@ -6,7 +6,7 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -class TranscriptionRequestQuote(UniversalBaseModel): +class TranscriptRequestQuote(UniversalBaseModel): """ What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free. """ diff --git a/src/arcmira/types/transcript_request_stage.py b/src/arcmira/types/transcript_request_stage.py new file mode 100644 index 0000000..8d10839 --- /dev/null +++ b/src/arcmira/types/transcript_request_stage.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptRequestStage = typing.Union[typing.Literal["queued", "transcribing", "analyzing"], typing.Any] diff --git a/src/arcmira/types/transcript_request_state.py b/src/arcmira/types/transcript_request_state.py new file mode 100644 index 0000000..d43d8ee --- /dev/null +++ b/src/arcmira/types/transcript_request_state.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptRequestState = typing.Union[typing.Literal["pending", "ready", "failed", "refunded"], typing.Any] diff --git a/src/arcmira/types/transcription_request_status.py b/src/arcmira/types/transcript_request_status.py similarity index 84% rename from src/arcmira/types/transcription_request_status.py rename to src/arcmira/types/transcript_request_status.py index c2a4eaa..c8f5c81 100644 --- a/src/arcmira/types/transcription_request_status.py +++ b/src/arcmira/types/transcript_request_status.py @@ -2,7 +2,7 @@ import typing -TranscriptionRequestStatus = typing.Union[ +TranscriptRequestStatus = typing.Union[ typing.Literal[ "queued", "downloading", "transcribing", "analyzing", "complete", "failed", "refund_pending", "refunded" ], diff --git a/src/arcmira/types/transcription_submit_response.py b/src/arcmira/types/transcript_request_submit_response.py similarity index 88% rename from src/arcmira/types/transcription_submit_response.py rename to src/arcmira/types/transcript_request_submit_response.py index e7f8562..cf71534 100644 --- a/src/arcmira/types/transcription_submit_response.py +++ b/src/arcmira/types/transcript_request_submit_response.py @@ -6,11 +6,11 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata -from .transcription_request import TranscriptionRequest +from .transcript_request import TranscriptRequest -class TranscriptionSubmitResponse(UniversalBaseModel): - request: TranscriptionRequest +class TranscriptRequestSubmitResponse(UniversalBaseModel): + request: TranscriptRequest existing: typing.Optional[bool] = pydantic.Field(default=None) """ True when an in-flight (or already-satisfied) request for the same video was returned instead of creating a new one. diff --git a/src/arcmira/types/transcription_request_charge_unit.py b/src/arcmira/types/transcription_request_charge_unit.py deleted file mode 100644 index 3518009..0000000 --- a/src/arcmira/types/transcription_request_charge_unit.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -TranscriptionRequestChargeUnit = typing.Union[typing.Literal["rows", "credits"], typing.Any] diff --git a/src/arcmira/types/transcription_request_stage.py b/src/arcmira/types/transcription_request_stage.py deleted file mode 100644 index 5285152..0000000 --- a/src/arcmira/types/transcription_request_stage.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -TranscriptionRequestStage = typing.Union[typing.Literal["queued", "transcribing", "analyzing"], typing.Any] diff --git a/src/arcmira/types/transcription_request_state.py b/src/arcmira/types/transcription_request_state.py deleted file mode 100644 index a237ad9..0000000 --- a/src/arcmira/types/transcription_request_state.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -TranscriptionRequestState = typing.Union[typing.Literal["pending", "ready", "failed", "refunded"], typing.Any] From 9ec5737940a488d296d5dc4c64a3e1f7f472fd71 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 23:54:46 -0700 Subject: [PATCH 03/12] Fail the fake server when Idempotency-Key is missing and assert the value sent --- tests/test_transcription.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_transcription.py b/tests/test_transcription.py index 816f660..3bd5c28 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -35,14 +35,16 @@ def answer(self): url = urlparse(self.path) query = parse_qs(url.query) body = self.rfile.read(int(self.headers.get('Content-Length', 0))).decode() - CALLS.append((url.path, query, dict(self.headers), body)) + CALLS.append((url.path, query, dict(self.headers), body, self.command)) status, extra = 200, {} if url.path.endswith('/quote'): result = QUOTE elif url.path == '/v1/transcriptions' and self.command == 'POST': key = self.headers['Idempotency-Key'] replay = key in RECEIPTS - if replay and RECEIPTS[key] != body: + if key is None: + status, result = 400, {'error': error('idempotency_key_required', 'invalid_request_error')} + elif replay and RECEIPTS[key] != body: status, result = 409, {'error': error('idempotency_conflict', 'conflict_error')} else: RECEIPTS[key] = body @@ -103,6 +105,8 @@ def test_preparation_exact_intent_replay_and_required_key(self): intent = dict(video_id='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0, idempotency_key='python-saved-intent') first = self.client.transcripts.with_raw_response.request(**intent) replay = self.client.transcripts.with_raw_response.request(**intent) + sent = [call[2]['Idempotency-Key'] for call in CALLS if call[0] == '/v1/transcriptions' and call[4] == 'POST' and 'Idempotency-Key' in call[2]] + self.assertEqual(sent[-2:], ['python-saved-intent', 'python-saved-intent']) self.assertEqual(first.status_code, 202) self.assertEqual(replay.status_code, 200) self.assertEqual(replay.data.request.id, first.data.request.id) From 8afed1560b79fe1f701184cdc6964520b9379d93 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 23:55:01 -0700 Subject: [PATCH 04/12] Cap pagination tests so a dropped cursor fails fast --- tests/test_transcription.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test_transcription.py b/tests/test_transcription.py index 3bd5c28..93e6ae4 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -1,4 +1,5 @@ import asyncio +import itertools import json import threading import unittest @@ -16,6 +17,7 @@ def error(code, kind): return dict(type=kind, code=code, message=code, doc_url='https://arcmira.com/docs/errors', request_id='fixture-request') +PAGE_CAP = 5 CURSOR = 'signed+/opaque==&cursor' PENDING = dict(state='pending', quality='premium', premium_job=dict(job_id=REQUEST['id'], status='queued', next_poll_seconds=5), status_url='/v1/transcriptions/'+REQUEST['id'], next_poll_seconds=5) CALLS = [] @@ -74,7 +76,7 @@ def setUpClass(cls): cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True) cls.thread.start() cls.base = f'http://127.0.0.1:{cls.server.server_port}' - cls.client = Arcmira(api_key='local-test-key', base_url=cls.base, max_retries=0) + cls.client = Arcmira(api_key='local-test-key', base_url=cls.base, max_retries=0, timeout=5) @classmethod def tearDownClass(cls): @@ -121,8 +123,9 @@ def test_preparation_exact_intent_replay_and_required_key(self): def test_request_and_episode_arrays_preserve_opaque_cursor(self): before = len(CALLS) - self.assertEqual([x.id for x in self.client.transcripts.list_requests(limit=1)], ['request-1','request-2']) - self.assertEqual([x.video_id for x in self.client.channels.videos.list(channel_id='UC-test', limit=1)], ['video-1','video-2']) + capped = lambda pager: list(itertools.islice(pager, PAGE_CAP)) + self.assertEqual([x.id for x in capped(self.client.transcripts.list_requests(limit=1))], ['request-1','request-2']) + self.assertEqual([x.video_id for x in capped(self.client.channels.videos.list(channel_id='UC-test', limit=1))], ['video-1','video-2']) continued = [call for call in CALLS[before:] if 'cursor' in call[1]] self.assertEqual(len(continued), 2) for call in continued: @@ -131,11 +134,14 @@ def test_request_and_episode_arrays_preserve_opaque_cursor(self): def test_async_pending_and_pagination(self): async def run(): - client = AsyncArcmira(api_key='local-test-key', base_url=self.base, max_retries=0) + client = AsyncArcmira(api_key='local-test-key', base_url=self.base, max_retries=0, timeout=5) pending = await client.transcripts.with_raw_response.get(video_id='pending0000', quality='premium') self.assertEqual(pending.status_code, 202) self.assertIsInstance(pending.data, TranscriptResult_Pending) - rows = [row.id async for row in await client.transcripts.list_requests(limit=1)] + rows = [] + async for row in await client.transcripts.list_requests(limit=1): + rows.append(row.id) + if len(rows) >= PAGE_CAP: break self.assertEqual(rows, ['request-1','request-2']) asyncio.run(run()) From dc172f82092b870a627988659691877bf9eadc6b Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 23:55:19 -0700 Subject: [PATCH 05/12] Lead ApiError text with status, code and message, preserved across regeneration --- scripts/install-generated.py | 4 ++++ scripts/overrides/api_error.py | 35 ++++++++++++++++++++++++++++++++++ src/arcmira/core/api_error.py | 16 ++++++++++++++-- tests/test_generation.py | 4 ++++ tests/test_transcription.py | 2 ++ 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 scripts/overrides/api_error.py diff --git a/scripts/install-generated.py b/scripts/install-generated.py index d75977f..74b63e0 100644 --- a/scripts/install-generated.py +++ b/scripts/install-generated.py @@ -13,6 +13,10 @@ dest.parent.mkdir(parents=True, exist_ok=True) dest.write_text(path.read_text().rstrip() + '\n') (target / 'py.typed').touch() +# Fern's ApiError.__str__ leads with headers; the override leads with status, code and message. +generated_error = (target / 'core/api_error.py').read_text() +assert 'class ApiError(Exception)' in generated_error +(target / 'core/api_error.py').write_text((root / 'scripts/overrides/api_error.py').read_text()) # The previous public pointer package exported these constants. init = target / '__init__.py' text = init.read_text() diff --git a/scripts/overrides/api_error.py b/scripts/overrides/api_error.py new file mode 100644 index 0000000..b12fce8 --- /dev/null +++ b/scripts/overrides/api_error.py @@ -0,0 +1,35 @@ +# Maintained by hand. scripts/install-generated.py copies this over the +# generated src/arcmira/core/api_error.py so regeneration keeps the format. + +from typing import Any, Dict, Optional + + +def _field(source: Any, name: str) -> Any: + if isinstance(source, dict): + return source.get(name) + return getattr(source, name, None) + + +class ApiError(Exception): + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + detail = _field(self.body, "error") or self.body + code = _field(detail, "code") + message = _field(detail, "message") + if code is not None and message is not None: + return f"{self.status_code} {code}: {message}" + return f"{self.status_code}: {self.body}" diff --git a/src/arcmira/core/api_error.py b/src/arcmira/core/api_error.py index 6f850a6..b12fce8 100644 --- a/src/arcmira/core/api_error.py +++ b/src/arcmira/core/api_error.py @@ -1,8 +1,15 @@ -# This file was auto-generated by Fern from our API Definition. +# Maintained by hand. scripts/install-generated.py copies this over the +# generated src/arcmira/core/api_error.py so regeneration keeps the format. from typing import Any, Dict, Optional +def _field(source: Any, name: str) -> Any: + if isinstance(source, dict): + return source.get(name) + return getattr(source, name, None) + + class ApiError(Exception): headers: Optional[Dict[str, str]] status_code: Optional[int] @@ -20,4 +27,9 @@ def __init__( self.body = body def __str__(self) -> str: - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" + detail = _field(self.body, "error") or self.body + code = _field(detail, "code") + message = _field(detail, "message") + if code is not None and message is not None: + return f"{self.status_code} {code}: {message}" + return f"{self.status_code}: {self.body}" diff --git a/tests/test_generation.py b/tests/test_generation.py index 231af0a..fdbd33c 100644 --- a/tests/test_generation.py +++ b/tests/test_generation.py @@ -27,4 +27,8 @@ def test_actual_contract_generates_union_key_and_collections(self): self.assertTrue(next(p for p in post['parameters'] if p['name']=='Idempotency-Key')['required']) self.assertIn('max_rows', post['requestBody']['content']['application/json']['schema']['required']) + def test_installed_api_error_matches_the_preserved_override(self): + self.assertEqual((ROOT / 'src/arcmira/core/api_error.py').read_text(), (ROOT / 'scripts/overrides/api_error.py').read_text()) + self.assertIn('overrides/api_error.py', (ROOT / 'scripts/install-generated.py').read_text()) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_transcription.py b/tests/test_transcription.py index 93e6ae4..0ead1bf 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -102,6 +102,8 @@ def test_quote_and_refusal(self): self.client.transcripts.get(video_id='refused0000', quality='premium') self.assertEqual(caught.exception.status_code, 403) self.assertEqual(caught.exception.body.quote, QUOTE) + self.assertEqual(str(caught.exception), '403 purchase_required: purchase_required') + self.assertNotIn('headers', str(caught.exception)) def test_preparation_exact_intent_replay_and_required_key(self): intent = dict(video_id='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0, idempotency_key='python-saved-intent') From 77fa2656693296becc061726c6b0fd0ec03f7394 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 23:55:28 -0700 Subject: [PATCH 06/12] License the Python SDK under Apache-2.0 --- LICENSE | 205 +++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 4 + pyproject.toml | 4 +- 3 files changed, 206 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index 4b7bc05..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,202 @@ -Copyright (c) 2026 Arcmira. All rights reserved. -This software is proprietary. No license is granted to copy, modify, -distribute, or create derivative works, except as needed to install -and use the published PyPI package in your own applications. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 251b512..2a9a80f 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,7 @@ uv build The tests use a local HTTP server. They check both client variants, state discrimination, response status, quotes, refusals, exact replay input, and opaque pagination. No live API key or purchase is required. Version 0.3.0 replaces the earlier URL-only placeholder with a usable SDK. The public URL constants remain available. + +## License + +Apache-2.0. See [LICENSE](./LICENSE). diff --git a/pyproject.toml b/pyproject.toml index f7ce30e..b6d2ea6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,8 @@ name = "arcmira" dynamic = ["version"] description = "The Arcmira Python SDK for YouTube and podcast transcripts." readme = "README.md" -license = { text = "UNLICENSED" } +license = "Apache-2.0" +license-files = ["LICENSE"] authors = [{ name = "Arcmira", email = "zeal@arcmira.com" }] requires-python = ">=3.9" dependencies = ["httpx>=0.21.2,<1", "pydantic>=1.9.2,<3", "typing_extensions>=4.0.0"] @@ -22,7 +23,6 @@ keywords = [ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", - "License :: Other/Proprietary License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", From 3fed1b58de828bd9b32c399420e15e2a69022215 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Thu, 1 Oct 2026 23:55:40 -0700 Subject: [PATCH 07/12] Keep numbered duplicate files out of the sdist and wheel --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b6d2ea6..aa3833d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,9 @@ include = [ exclude = [ "/.gitignore", "/.github", + "**/* [0-9].py", ] [tool.hatch.build.targets.wheel] packages = ["src/arcmira"] +exclude = ["**/* [0-9].py"] From bebe25d4e26fe9147fa371ba47f2085f127db4a7 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Fri, 2 Oct 2026 00:43:15 -0700 Subject: [PATCH 08/12] Regenerate the SDK from the frozen v1 contract The transcription purchase is one Job. A Premium read can answer preparation_required, the purchase key and max_rows are optional at zero dollars, and the request body no longer sends the deprecated videoId alias. --- fern/openapi.json | 1000 +++++++++-------- reference.md | 194 ++-- scripts/prepare-openapi.py | 21 +- src/arcmira/__init__.py | 93 +- src/arcmira/channels/guests/client.py | 8 +- src/arcmira/channels/guests/raw_client.py | 8 +- src/arcmira/channels/related/client.py | 40 +- src/arcmira/channels/related/raw_client.py | 40 +- src/arcmira/channels/videos/client.py | 8 +- src/arcmira/channels/videos/raw_client.py | 8 +- src/arcmira/corrections/client.py | 10 +- src/arcmira/corrections/raw_client.py | 17 +- .../entities/recommendations/client.py | 4 +- .../entities/recommendations/raw_client.py | 4 +- src/arcmira/errors/__init__.py | 3 - .../errors/precondition_failed_error.py | 4 +- .../errors/unprocessable_entity_error.py | 11 - src/arcmira/feedback/client.py | 6 +- src/arcmira/feedback/raw_client.py | 4 +- src/arcmira/mentions/client.py | 4 +- src/arcmira/mentions/raw_client.py | 4 +- src/arcmira/monitors/alerts/client.py | 18 +- src/arcmira/monitors/alerts/raw_client.py | 18 +- src/arcmira/monitors/client.py | 24 +- src/arcmira/monitors/raw_client.py | 16 +- src/arcmira/monitors/trackers/client.py | 6 +- src/arcmira/monitors/trackers/raw_client.py | 4 +- src/arcmira/organizations/related/client.py | 40 +- .../organizations/related/raw_client.py | 40 +- src/arcmira/people/appearances/client.py | 8 +- src/arcmira/people/appearances/raw_client.py | 8 +- src/arcmira/people/related/client.py | 40 +- src/arcmira/people/related/raw_client.py | 40 +- src/arcmira/products/related/client.py | 40 +- src/arcmira/products/related/raw_client.py | 40 +- src/arcmira/recommendations/client.py | 4 +- src/arcmira/recommendations/raw_client.py | 4 +- src/arcmira/topics/related/client.py | 40 +- src/arcmira/topics/related/raw_client.py | 40 +- src/arcmira/trackers/alerts/client.py | 18 +- src/arcmira/trackers/alerts/raw_client.py | 18 +- src/arcmira/trackers/client.py | 18 +- src/arcmira/trackers/raw_client.py | 12 +- src/arcmira/transcripts/client.py | 108 +- src/arcmira/transcripts/edits/client.py | 6 +- src/arcmira/transcripts/edits/raw_client.py | 4 +- src/arcmira/transcripts/merges/client.py | 6 +- src/arcmira/transcripts/merges/raw_client.py | 4 +- src/arcmira/transcripts/raw_client.py | 137 +-- src/arcmira/transcripts/speakers/client.py | 6 +- .../transcripts/speakers/raw_client.py | 4 +- src/arcmira/types/__init__.py | 96 +- src/arcmira/types/alert_list_response.py | 2 +- .../types/channel_guest_list_response.py | 14 - .../types/channel_sponsors_response_access.py | 10 + .../types/correction_seq_mismatch_response.py | 36 - .../types/entity_channel_list_response.py | 14 - .../types/entity_momentum_response_access.py | 10 + .../entity_organization_list_response.py | 14 - .../types/entity_people_list_response.py | 14 - .../types/entity_product_list_response.py | 14 - .../types/entity_topic_list_response.py | 14 - src/arcmira/types/error_error.py | 10 + .../types/person_appearance_list_response.py | 14 - src/arcmira/types/transcript_job.py | 90 ++ src/arcmira/types/transcript_job_charge.py | 43 + .../types/transcript_job_charge_from.py | 5 + .../types/transcript_job_charge_unit.py | 5 + src/arcmira/types/transcript_job_stage.py | 5 + src/arcmira/types/transcript_job_state.py | 5 + ...est_status.py => transcript_job_status.py} | 2 +- src/arcmira/types/transcript_pending.py | 7 +- .../types/transcript_preparation_required.py | 38 + .../transcript_preparation_required_action.py | 27 + ...cript_preparation_required_action_body.py} | 7 +- ...ript_preparation_required_action_method.py | 5 + ...ript_preparation_required_last_attempt.py} | 10 +- ...transcript_preparation_required_quality.py | 5 + .../transcript_preparation_required_quote.py | 38 + ...cript_preparation_required_quote_charge.py | 36 + ..._preparation_required_quote_charge_from.py | 7 + ..._preparation_required_quote_charge_unit.py | 5 + .../types/transcript_purchase_quote.py | 6 + .../types/transcript_purchase_quote_charge.py | 11 + .../transcript_purchase_quote_charge_from.py | 5 + ...y => transcript_purchase_quote_upgrade.py} | 15 +- src/arcmira/types/transcript_request.py | 113 -- .../types/transcript_request_charge_unit.py | 5 - ...ipt_request_list_response_requests_item.py | 4 +- src/arcmira/types/transcript_request_stage.py | 5 - src/arcmira/types/transcript_request_state.py | 5 - .../transcript_request_submit_response.py | 22 +- src/arcmira/types/transcript_response.py | 8 +- .../types/transcript_response_access.py | 10 + .../types/transcript_response_premium_job.py | 41 - src/arcmira/types/transcript_result.py | 35 +- .../transcript_search_response_access.py | 10 + tests/fixtures/transcription-responses.json | 274 +++-- tests/test_generation.py | 10 +- tests/test_transcription.py | 18 +- 100 files changed, 1913 insertions(+), 1548 deletions(-) delete mode 100644 src/arcmira/errors/unprocessable_entity_error.py delete mode 100644 src/arcmira/types/correction_seq_mismatch_response.py create mode 100644 src/arcmira/types/transcript_job.py create mode 100644 src/arcmira/types/transcript_job_charge.py create mode 100644 src/arcmira/types/transcript_job_charge_from.py create mode 100644 src/arcmira/types/transcript_job_charge_unit.py create mode 100644 src/arcmira/types/transcript_job_stage.py create mode 100644 src/arcmira/types/transcript_job_state.py rename src/arcmira/types/{transcript_request_status.py => transcript_job_status.py} (85%) create mode 100644 src/arcmira/types/transcript_preparation_required.py create mode 100644 src/arcmira/types/transcript_preparation_required_action.py rename src/arcmira/types/{transcript_pending_premium_job.py => transcript_preparation_required_action_body.py} (74%) create mode 100644 src/arcmira/types/transcript_preparation_required_action_method.py rename src/arcmira/types/{transcript_request_charge.py => transcript_preparation_required_last_attempt.py} (62%) create mode 100644 src/arcmira/types/transcript_preparation_required_quality.py create mode 100644 src/arcmira/types/transcript_preparation_required_quote.py create mode 100644 src/arcmira/types/transcript_preparation_required_quote_charge.py create mode 100644 src/arcmira/types/transcript_preparation_required_quote_charge_from.py create mode 100644 src/arcmira/types/transcript_preparation_required_quote_charge_unit.py create mode 100644 src/arcmira/types/transcript_purchase_quote_charge_from.py rename src/arcmira/types/{transcript_request_quote.py => transcript_purchase_quote_upgrade.py} (55%) delete mode 100644 src/arcmira/types/transcript_request.py delete mode 100644 src/arcmira/types/transcript_request_charge_unit.py delete mode 100644 src/arcmira/types/transcript_request_stage.py delete mode 100644 src/arcmira/types/transcript_request_state.py delete mode 100644 src/arcmira/types/transcript_response_premium_job.py diff --git a/fern/openapi.json b/fern/openapi.json index 2c387fc..78a763a 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -320,6 +320,14 @@ "type": "integer", "description": "Present on rate gates. Mirrors the Retry-After header." }, + "current_revision": { + "type": "string", + "description": "On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction." + }, + "expected_seq": { + "type": "integer", + "description": "On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key." + }, "doc_url": { "type": "string" }, @@ -345,6 +353,11 @@ "type": "not_found", "description": "A referenced alert row does not exist or belongs to another account." }, + { + "code": "anchor_mismatch", + "type": "conflict_error", + "description": "The anchored lines no longer hash to anchor.contentHash. current_revision is the revision to re-read; send a re-anchored correction under a new key." + }, { "code": "api_not_enabled", "type": "permission_error", @@ -454,7 +467,7 @@ { "code": "invalid_idempotency_key", "type": "invalid_request_error", - "description": "Use a valid monitor creation request key." + "description": "Idempotency-Key must be 1 to 255 printable ASCII characters (0x21 to 0x7E). param is Idempotency-Key." }, { "code": "invalid_query", @@ -523,6 +536,11 @@ "gate": "pagination", "description": "Rows past the free window need a paid plan." }, + { + "code": "paid_plan_required", + "type": "permission_error", + "description": "The operation needs a paid plan. unlock.url names the plan that does." + }, { "code": "premium_transcript_requested", "type": "permission_error", @@ -530,9 +548,9 @@ "description": "Premium transcript text needs a plan with Premium transcripts." }, { - "code": "purchase_required", - "type": "permission_error", - "description": "An explicit whole-video purchase is required. Read quote_url and submit max_rows with Idempotency-Key." + "code": "purchase_authority_changed", + "type": "conflict_error", + "description": "The account billing authority (plan, team, or on-demand controls) changed after the intent was accepted. Nothing was charged. Review the quote and send a new intent." }, { "code": "quota_exceeded", @@ -562,6 +580,11 @@ "type": "conflict_error", "description": "The operation conflicts with existing resource state." }, + { + "code": "revision_mismatch", + "type": "conflict_error", + "description": "The transcript changed after the revision the correction echoed. current_revision is the revision to re-read; send a re-anchored correction under a new key." + }, { "code": "scope_too_broad", "type": "invalid_request_error", @@ -572,6 +595,11 @@ "type": "server_error", "description": "Transcript search is unavailable (HTTP 503). Retry-After carries the wait." }, + { + "code": "sequence_mismatch", + "type": "conflict_error", + "description": "HTTP 412. The correction seq is not the next one for this video. expected_seq is the next seq; rebase and resend under the same key." + }, { "code": "server_error", "type": "server_error", @@ -587,6 +615,16 @@ "type": "rate_limit_error", "description": "Verification code sends hit a per-address, per-IP, or per-client cap. Retry-After carries the wait." }, + { + "code": "speaker_not_correctable", + "type": "conflict_error", + "description": "The speaker shares a voice track or was named in review, so a correction cannot land on their lines alone." + }, + { + "code": "spend_limit_exceeded", + "type": "quota_exceeded", + "description": "The purchase would take on-demand spending past the account or seat spend limit this period. Nothing was charged. Raise the limit or wait for the next period, then send a new intent." + }, { "code": "team_key_required", "type": "permission_error", @@ -3032,6 +3070,14 @@ "type": "integer", "description": "Present on rate gates. Mirrors the Retry-After header." }, + "current_revision": { + "type": "string", + "description": "On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction." + }, + "expected_seq": { + "type": "integer", + "description": "On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key." + }, "doc_url": { "type": "string" }, @@ -3365,6 +3411,14 @@ "type": "integer", "description": "Present on rate gates. Mirrors the Retry-After header." }, + "current_revision": { + "type": "string", + "description": "On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction." + }, + "expected_seq": { + "type": "integer", + "description": "On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key." + }, "doc_url": { "type": "string" }, @@ -3838,6 +3892,14 @@ "type": "integer", "description": "Present on rate gates. Mirrors the Retry-After header." }, + "current_revision": { + "type": "string", + "description": "On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction." + }, + "expected_seq": { + "type": "integer", + "description": "On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key." + }, "doc_url": { "type": "string" }, @@ -6395,18 +6457,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -6425,9 +6479,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "_meta" @@ -6479,18 +6531,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -6544,9 +6588,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "exportCapabilities", @@ -6615,18 +6657,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -6688,9 +6722,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "peopleMode", @@ -6765,18 +6797,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -6830,9 +6854,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "exportCapabilities", @@ -6914,18 +6936,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -6979,9 +6993,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "exportCapabilities", @@ -7040,18 +7052,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -7105,9 +7109,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "exportCapabilities", @@ -9533,18 +9535,10 @@ "type": "integer", "description": "Rows matching the filter across all pages." }, - "offset": { - "type": "integer", - "description": "Row offset of this page, as the cursor encoded it. 0 on the first page." - }, "limit": { "type": "integer", "description": "Page size applied, after the plan clamp." }, - "hasMore": { - "type": "boolean", - "description": "Same value as has_more, kept for readers of the web shape." - }, "has_more": { "type": "boolean", "description": "True when more rows exist past this page." @@ -9598,9 +9592,7 @@ "required": [ "items", "total", - "offset", "limit", - "hasMore", "has_more", "next_cursor", "exportCapabilities", @@ -10018,7 +10010,7 @@ }, "has_more": { "type": "boolean", - "description": "CURRENTLY always false: this endpoint returns the newest n alerts as a single page and does not paginate." + "description": "True when older alerts exist past limit. The endpoint does not paginate: raise limit, up to 100, to read them." }, "next_cursor": { "type": "null", @@ -10849,41 +10841,7 @@ "description": "When the transcript was produced." }, "premium_job": { - "type": "object", - "properties": { - "job_id": { - "type": [ - "string", - "null" - ], - "description": "Transcription request id. Poll it with GET /v1/transcriptions/{id}." - }, - "status": { - "type": "string", - "description": "Pipeline status at submit time." - }, - "next_poll_seconds": { - "type": [ - "integer", - "null" - ], - "description": "Seconds to wait before polling again." - }, - "eta_seconds": { - "type": [ - "integer", - "null" - ], - "description": "Estimated seconds until the Premium transcript is ready." - } - }, - "required": [ - "job_id", - "status", - "next_poll_seconds", - "eta_seconds" - ], - "description": "Reserved for job metadata. Pending Premium retrieval uses its separate 202 response." + "$ref": "#/components/schemas/TranscriptionJob" }, "access": { "type": "object", @@ -10986,6 +10944,14 @@ "type": "integer", "description": "Present on rate gates. Mirrors the Retry-After header." }, + "current_revision": { + "type": "string", + "description": "On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction." + }, + "expected_seq": { + "type": "integer", + "description": "On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key." + }, "doc_url": { "type": "string" }, @@ -11187,13 +11153,130 @@ "generated" ] }, - "TranscriptPending": { + "TranscriptionJob": { "type": "object", "properties": { + "id": { + "type": "string", + "description": "Transcription request id (UUID)." + }, + "video_id": { + "type": "string", + "description": "YouTube video id (11 characters)." + }, "state": { "type": "string", "enum": [ - "pending" + "pending", + "ready", + "failed", + "refunded" + ], + "description": "Coarse outcome: pending until the Premium transcript is servable (ready), the purchase failed, or it was refunded." + }, + "status": { + "type": "string", + "enum": [ + "queued", + "downloading", + "transcribing", + "analyzing", + "complete", + "failed", + "refund_pending", + "refunded" + ], + "description": "Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked)." + }, + "stage": { + "type": [ + "string", + "null" + ], + "enum": [ + "queued", + "transcribing", + "analyzing", + null + ], + "description": "User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses." + }, + "charge": { + "type": "object", + "properties": { + "unit": { + "type": "string", + "enum": [ + "credits" + ] + }, + "amount": { + "type": "number", + "description": "Credits this purchase charged. 0 when a prior unlock made it free." + }, + "from": { + "type": "string", + "enum": [ + "included", + "on_demand", + "mixed" + ], + "description": "Where the credits came from: the included allowance, on-demand usage, or both. Present once the purchase is funded." + } + }, + "required": [ + "unit", + "amount" + ], + "description": "What the purchase charged. Present on durable purchases; absent only on legacy requests." + }, + "eta_seconds": { + "type": "integer", + "description": "Estimated seconds until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight." + }, + "next_poll_seconds": { + "type": "integer", + "description": "Seconds to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight." + }, + "error": { + "type": "string", + "description": "Failure reason. Only present when state is failed or refunded." + }, + "refunded": { + "type": "boolean", + "description": "True when the charge was returned. Only present when state is failed or refunded." + }, + "created_at": { + "type": "string", + "description": "When the request was submitted." + }, + "completed_at": { + "type": "string", + "description": "When the request reached a terminal status. Absent while in flight." + }, + "status_url": { + "type": "string", + "description": "Absolute URL of GET /v1/transcriptions/{id} for this job." + } + }, + "required": [ + "id", + "video_id", + "state", + "status", + "stage", + "created_at", + "status_url" + ], + "description": "Your open Premium purchase for this video, when captions were served while it prepares." + }, + "TranscriptPreparationRequired": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "preparation_required" ] }, "quality": { @@ -11202,45 +11285,155 @@ "premium" ] }, - "premium_job": { + "video_id": { + "type": "string" + }, + "quote": { + "type": [ + "object", + "null" + ], + "properties": { + "rows": { + "type": "integer", + "description": "Whole-video row-equivalent price. 0 when a prior unlock makes it free." + }, + "charge": { + "type": "object", + "properties": { + "unit": { + "type": "string", + "enum": [ + "credits" + ] + }, + "amount": { + "type": "number", + "description": "Credits the purchase would charge." + }, + "from": { + "type": "string", + "enum": [ + "included", + "on_demand", + "mixed" + ], + "description": "Where those credits would come from at the current balance." + } + }, + "required": [ + "unit", + "amount", + "from" + ] + }, + "eligible": { + "type": "boolean", + "description": "False when the account cannot buy right now; the POST then answers with the reason." + }, + "max_on_demand_cents": { + "type": "integer", + "description": "On-demand money in whole cents the purchase would need. 0 means included credits cover it." + } + }, + "required": [ + "rows", + "charge", + "eligible", + "max_on_demand_cents" + ], + "description": "Null when the video has no known duration or is longer than 12 hours; the POST refuses it for the same reason." + }, + "action": { "type": "object", "properties": { - "job_id": { + "method": { + "type": "string", + "enum": [ + "POST" + ] + }, + "url": { "type": "string" }, + "body": { + "type": "object", + "properties": { + "video_id": { + "type": "string" + } + }, + "required": [ + "video_id" + ] + } + }, + "required": [ + "method", + "url", + "body" + ], + "description": "The one request that prepares Premium from included credits and moves no money." + }, + "last_attempt": { + "type": "object", + "properties": { "status": { "type": "string" }, - "next_poll_seconds": { - "type": "number" - }, - "eta_seconds": { - "type": [ - "number", - "null" - ] + "error": { + "type": "string" } }, "required": [ - "job_id", "status", - "next_poll_seconds", - "eta_seconds" + "error" + ], + "description": "The most recent failed or refunded purchase of this video, when there is one." + } + }, + "required": [ + "state", + "quality", + "video_id", + "quote", + "action" + ] + }, + "TranscriptPending": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "pending" ] }, - "status_url": { + "quality": { + "type": "string", + "enum": [ + "premium" + ] + }, + "video_id": { "type": "string" }, - "next_poll_seconds": { - "type": "number" + "job": { + "allOf": [ + { + "$ref": "#/components/schemas/TranscriptionJob" + }, + { + "description": "The open purchase. Poll job.status_url, or read this transcript again, after Retry-After." + } + ] } }, "required": [ "state", "quality", - "premium_job", - "status_url", - "next_poll_seconds" + "video_id", + "job" ] }, "TranscriptPurchaseQuote": { @@ -11264,6 +11457,22 @@ "eligible": { "type": "boolean" }, + "upgrade": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "href": { + "type": "string" + } + }, + "required": [ + "label", + "href" + ], + "description": "Present when eligible is false: the plan checkout that can buy this transcript, as a button label and an absolute link." + }, "quote": { "$ref": "#/components/schemas/TranscriptQuote" }, @@ -11279,11 +11488,21 @@ }, "amount": { "type": "number" + }, + "from": { + "type": "string", + "enum": [ + "included", + "on_demand", + "mixed" + ], + "description": "Where the charge would come from at the current balance." } }, "required": [ "unit", - "amount" + "amount", + "from" ] }, "credits_per_row": { @@ -11379,139 +11598,17 @@ "TranscriptionSubmitResponse": { "type": "object", "properties": { - "request": { - "$ref": "#/components/schemas/TranscriptionRequest" + "job": { + "$ref": "#/components/schemas/TranscriptionJob" }, "existing": { "type": "boolean", - "description": "True when an in-flight (or already-satisfied) request for the same video was returned instead of creating a new one." - }, - "overLimit": { - "type": "boolean", - "description": "Only present (true) when this purchase consumed the rest of the included row allocation." - } - }, - "required": [ - "request" - ] - }, - "TranscriptionRequest": { - "type": "object", - "properties": { - "id": { - "type": [ - "string", - "null" - ], - "description": "Transcription request id (UUID). Null only in the degenerate submit response for a video you already own that has no request history." - }, - "videoId": { - "type": "string", - "description": "YouTube video id (11 characters)." - }, - "status": { - "type": "string", - "enum": [ - "queued", - "downloading", - "transcribing", - "analyzing", - "complete", - "failed", - "refund_pending", - "refunded" - ], - "description": "Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked)." - }, - "state": { - "type": "string", - "enum": [ - "pending", - "ready", - "failed", - "refunded" - ] - }, - "charge": { - "type": "object", - "properties": { - "unit": { - "type": "string", - "enum": [ - "rows", - "credits" - ] - }, - "amount": { - "type": "number" - }, - "credits_per_row": { - "type": "number" - } - }, - "required": [ - "unit", - "amount", - "credits_per_row" - ], - "description": "Accepted charge units. Present on durable purchases; absent only on legacy requests." - }, - "stage": { - "type": [ - "string", - "null" - ], - "enum": [ - "queued", - "transcribing", - "analyzing", - null - ], - "description": "User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses." - }, - "quote": { - "allOf": [ - { - "$ref": "#/components/schemas/TranscriptQuote" - }, - { - "description": "What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free." - } - ] - }, - "etaSeconds": { - "type": "integer", - "description": "Estimated SECONDS until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight." - }, - "nextPollSeconds": { - "type": "integer", - "description": "Recommended SECONDS to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight." - }, - "error": { - "type": "string", - "description": "Failure reason. Only present when status is failed or refunded." - }, - "refunded": { - "type": "boolean", - "description": "True when the charged rows were returned. Only present when status is failed or refunded." - }, - "createdAt": { - "type": "string", - "description": "When the request was submitted." - }, - "completedAt": { - "type": "string", - "description": "When the request reached a terminal status. Absent while in flight." + "description": "True when a request for this video already existed (in flight or ready) and was returned instead of creating a new one." } }, "required": [ - "id", - "videoId", - "status", - "state", - "stage", - "quote", - "createdAt" + "job", + "existing" ] }, "TranscriptionListResponse": { @@ -11522,7 +11619,7 @@ "items": { "allOf": [ { - "$ref": "#/components/schemas/TranscriptionRequest" + "$ref": "#/components/schemas/TranscriptionJob" }, { "type": "object", @@ -11595,23 +11692,6 @@ "result" ] }, - "CorrectionSeqMismatchResponse": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Always \"Out-of-order correction.\"." - }, - "expectedSeq": { - "type": "integer", - "description": "The seq the server expects next for this video. Rebase local counters onto it and resend." - } - }, - "required": [ - "error", - "expectedSeq" - ] - }, "WithdrawnResponse": { "type": "object", "properties": { @@ -13125,7 +13205,7 @@ ], "operationId": "list_entity_recommendations", "summary": "List recommendations for an entity", - "description": "Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded.", + "description": "Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded.", "security": [ { "bearerAuth": [] @@ -13305,7 +13385,7 @@ ], "operationId": "list_mentions", "summary": "Search mentions across media", - "description": "Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.", + "description": "Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.", "security": [ { "bearerAuth": [] @@ -13509,7 +13589,7 @@ ], "operationId": "list_recommendations", "summary": "Search recommendations across media", - "description": "Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated.", + "description": "Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated.", "security": [ { "bearerAuth": [] @@ -13756,7 +13836,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." } ], "requestBody": { @@ -14595,7 +14676,7 @@ ], "operationId": "list_channel_videos", "summary": "Newest indexed videos of a channel", - "description": "The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "description": "The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", "security": [ { "bearerAuth": [] @@ -14628,10 +14709,10 @@ { "schema": { "type": "string", - "description": "Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor." + "description": "Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor." }, "required": false, - "description": "Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor.", + "description": "Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor.", "name": "cursor", "in": "query" }, @@ -14973,7 +15054,7 @@ ], "operationId": "list_person_appearances", "summary": "List person appearances", - "description": "Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead.", + "description": "Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead.", "security": [ { "bearerAuth": [] @@ -15004,10 +15085,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -15145,7 +15226,7 @@ ], "operationId": "list_person_topics", "summary": "List topics related to a person", - "description": "The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -15176,10 +15257,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -15317,7 +15398,7 @@ ], "operationId": "list_person_people", "summary": "List people related to a person", - "description": "The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -15348,10 +15429,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -15489,7 +15570,7 @@ ], "operationId": "list_person_organizations", "summary": "List organizations related to a person", - "description": "The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -15520,10 +15601,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -15661,7 +15742,7 @@ ], "operationId": "list_person_products", "summary": "List products related to a person", - "description": "The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -15692,10 +15773,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -15833,7 +15914,7 @@ ], "operationId": "list_person_channels", "summary": "List channels related to a person", - "description": "The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -15864,10 +15945,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -16081,7 +16162,7 @@ ], "operationId": "list_topic_topics", "summary": "List topics related to a topic", - "description": "The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -16112,10 +16193,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -16253,7 +16334,7 @@ ], "operationId": "list_topic_people", "summary": "List people related to a topic", - "description": "The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -16284,10 +16365,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -16425,7 +16506,7 @@ ], "operationId": "list_topic_organizations", "summary": "List organizations related to a topic", - "description": "The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -16456,10 +16537,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -16597,7 +16678,7 @@ ], "operationId": "list_topic_products", "summary": "List products related to a topic", - "description": "The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -16628,10 +16709,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -16769,7 +16850,7 @@ ], "operationId": "list_topic_channels", "summary": "List channels related to a topic", - "description": "The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -16800,10 +16881,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -17017,7 +17098,7 @@ ], "operationId": "list_organization_topics", "summary": "List topics related to a organization", - "description": "The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -17048,10 +17129,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -17189,7 +17270,7 @@ ], "operationId": "list_organization_people", "summary": "List people related to a organization", - "description": "The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -17220,10 +17301,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -17361,7 +17442,7 @@ ], "operationId": "list_organization_organizations", "summary": "List organizations related to a organization", - "description": "The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -17392,10 +17473,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -17533,7 +17614,7 @@ ], "operationId": "list_organization_products", "summary": "List products related to a organization", - "description": "The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -17564,10 +17645,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -17705,7 +17786,7 @@ ], "operationId": "list_organization_channels", "summary": "List channels related to a organization", - "description": "The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -17736,10 +17817,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -17953,7 +18034,7 @@ ], "operationId": "list_product_topics", "summary": "List topics related to a product", - "description": "The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -17984,10 +18065,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -18125,7 +18206,7 @@ ], "operationId": "list_product_people", "summary": "List people related to a product", - "description": "The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -18156,10 +18237,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -18297,7 +18378,7 @@ ], "operationId": "list_product_organizations", "summary": "List organizations related to a product", - "description": "The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -18328,10 +18409,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -18469,7 +18550,7 @@ ], "operationId": "list_product_products", "summary": "List products related to a product", - "description": "The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -18500,10 +18581,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -18641,7 +18722,7 @@ ], "operationId": "list_product_channels", "summary": "List channels related to a product", - "description": "The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -18672,10 +18753,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -18890,7 +18971,7 @@ ], "operationId": "list_channel_topics", "summary": "List topics related to a channel", - "description": "The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -18921,10 +19002,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -19062,7 +19143,7 @@ ], "operationId": "list_channel_people", "summary": "List people related to a channel", - "description": "The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -19093,10 +19174,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -19234,7 +19315,7 @@ ], "operationId": "list_channel_organizations", "summary": "List organizations related to a channel", - "description": "The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -19265,10 +19346,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -19406,7 +19487,7 @@ ], "operationId": "list_channel_products", "summary": "List products related to a channel", - "description": "The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -19437,10 +19518,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -19578,7 +19659,7 @@ ], "operationId": "list_channel_channels", "summary": "List channels related to a channel", - "description": "The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -19609,10 +19690,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -19750,7 +19831,7 @@ ], "operationId": "list_channel_guests", "summary": "List channel guests", - "description": "People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape.", + "description": "People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.", "security": [ { "bearerAuth": [] @@ -19781,10 +19862,10 @@ { "schema": { "type": "string", - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live." }, "required": false, - "description": "Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", + "description": "Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.", "name": "cursor", "in": "query" }, @@ -19996,7 +20077,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "requestBody": { @@ -20197,7 +20279,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "requestBody": { @@ -20375,7 +20458,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "responses": { @@ -20479,7 +20563,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "responses": { @@ -20654,7 +20739,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "requestBody": { @@ -20759,7 +20845,7 @@ ], "operationId": "list_monitor_alerts", "summary": "List recent monitor alerts", - "description": "The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id (\"ent_{n}\") and mention_id (\"men_{n}\") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.", + "description": "The newest limit alert deliveries for the monitor (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id (\"ent_{n}\") and mention_id (\"men_{n}\") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.", "security": [ { "bearerAuth": [] @@ -20781,10 +20867,11 @@ "type": "integer", "minimum": 1, "maximum": 100, - "default": 25 + "description": "Alerts to return, newest first, 1 to 100. Default 25." }, "required": false, - "name": "n", + "description": "Alerts to return, newest first, 1 to 100. Default 25.", + "name": "limit", "in": "query" } ], @@ -20918,7 +21005,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "requestBody": { @@ -21126,7 +21214,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "requestBody": { @@ -21286,7 +21375,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation." } ], "responses": { @@ -21366,7 +21456,7 @@ ], "operationId": "list_tracker_alerts", "summary": "List recent tracker alerts", - "description": "The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id (\"ent_{n}\") and mention_id (\"men_{n}\") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.", + "description": "The newest limit alert deliveries for the tracker (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id (\"ent_{n}\") and mention_id (\"men_{n}\") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.", "security": [ { "bearerAuth": [] @@ -21388,10 +21478,11 @@ "type": "integer", "minimum": 1, "maximum": 100, - "default": 25 + "description": "Alerts to return, newest first, 1 to 100. Default 25." }, "required": false, - "name": "n", + "description": "Alerts to return, newest first, 1 to 100. Default 25.", + "name": "limit", "in": "query" } ], @@ -21675,7 +21766,7 @@ ], "operationId": "get_transcript", "summary": "Get a video transcript", - "description": "Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections.", + "description": "Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. Branch on state: 200 ready is owned content; 202 pending carries the job, with Retry-After; 200 preparation_required carries the whole-video quote and the action to take, POST /v1/transcriptions with { video_id }, plus last_attempt when the previous purchase failed. Plans without Premium read captions with an access gate instead. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections.", "security": [ { "bearerAuth": [] @@ -21699,10 +21790,10 @@ "captions", "premium" ], - "description": "captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings." + "description": "captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings." }, "required": false, - "description": "captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings.", + "description": "captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings.", "name": "quality", "in": "query" }, @@ -21780,7 +21871,7 @@ ], "responses": { "200": { - "description": "The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note.", + "description": "state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state preparation_required: Premium is not owned yet; the quote and the POST that prepares it.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" @@ -21801,13 +21892,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TranscriptResponse" + "oneOf": [ + { + "$ref": "#/components/schemas/TranscriptResponse" + }, + { + "$ref": "#/components/schemas/TranscriptPreparationRequired" + } + ], + "discriminator": { + "propertyName": "state", + "mapping": { + "ready": "#/components/schemas/TranscriptResponse", + "preparation_required": "#/components/schemas/TranscriptPreparationRequired" + } + } } } } }, "202": { - "description": "Owned purchase is pending; no transcript content or charge.", + "description": "state pending: the Premium purchase is in flight; job carries its status and poll interval. No transcript content or charge.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" @@ -21885,7 +21990,7 @@ ], "operationId": "quote_transcription", "summary": "Quote a whole-video Premium purchase", - "description": "Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode.", + "description": "Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. A video with no known duration, or one past the 12 hour cap, answers 400 invalid_query with param video_id.", "security": [ { "bearerAuth": [] @@ -22067,7 +22172,7 @@ ], "operationId": "submit_transcription", "summary": "Submit a YouTube video for transcription", - "description": "Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201.", + "description": "Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it.", "security": [ { "bearerAuth": [] @@ -22078,10 +22183,11 @@ "schema": { "type": "string" }, - "required": true, + "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." } ], "requestBody": { @@ -22090,32 +22196,37 @@ "schema": { "type": "object", "properties": { - "max_on_demand_cents": { - "type": "number", - "minimum": 0, - "default": 0, - "description": "Maximum new monetary on-demand charge in cents. Omit to authorize none." - }, - "max_rows": { - "type": "integer", - "minimum": 0, - "maximum": 3600, - "description": "Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote." + "video_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{11}$", + "description": "YouTube video id (11 characters). Either video_id or url is required.", + "example": "dQw4w9WgXcQ" }, "videoId": { "type": "string", "pattern": "^[A-Za-z0-9_-]{11}$", - "description": "YouTube video id (11 characters). Either videoId or url is required." + "description": "Alias of video_id for one release.", + "deprecated": true }, "url": { "type": "string", "format": "uri", - "description": "A YouTube watch/short/live URL. Either videoId or url is required." + "description": "A YouTube watch/short/live URL. Either video_id or url is required." + }, + "max_rows": { + "type": "integer", + "minimum": 0, + "maximum": 3600, + "description": "Maximum whole-video rows authorized. Credit mode charges four credits per row. Omit it to cap the purchase at the current quote. Required with max_on_demand_cents above 0.", + "example": 300 + }, + "max_on_demand_cents": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "Maximum new monetary on-demand charge in whole cents. Defaults to 0, which moves no money. Above 0 it requires Idempotency-Key and max_rows." } }, - "required": [ - "max_rows" - ], "additionalProperties": false } } @@ -22123,40 +22234,7 @@ }, "responses": { "200": { - "description": "An existing in-flight or already-satisfied request was returned (existing: true)", - "headers": { - "X-Request-Id": { - "$ref": "#/components/headers/X-Request-Id" - }, - "X-Arcmira-Version": { - "$ref": "#/components/headers/X-Arcmira-Version" - }, - "RateLimit-Limit": { - "$ref": "#/components/headers/RateLimit-Limit" - }, - "RateLimit-Remaining": { - "$ref": "#/components/headers/RateLimit-Remaining" - }, - "RateLimit-Reset": { - "$ref": "#/components/headers/RateLimit-Reset" - }, - "Retry-After": { - "$ref": "#/components/headers/Retry-After" - }, - "Idempotency-Replayed": { - "$ref": "#/components/headers/Idempotency-Replayed" - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TranscriptionSubmitResponse" - } - } - } - }, - "201": { - "description": "Purchase ready", + "description": "job.state is ready, failed or refunded: the transcript is servable, or the purchase ended without one.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" @@ -22189,7 +22267,7 @@ } }, "202": { - "description": "Purchase funded; generation pending", + "description": "job.state is pending: generation is under way. Poll job.status_url after Retry-After.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" @@ -22254,24 +22332,6 @@ } } }, - "422": { - "description": "Duration unavailable or video too long (12h cap)", - "headers": { - "X-Request-Id": { - "$ref": "#/components/headers/X-Request-Id" - }, - "X-Arcmira-Version": { - "$ref": "#/components/headers/X-Arcmira-Version" - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "429": { "$ref": "#/components/responses/RateLimited" }, @@ -22286,7 +22346,7 @@ ], "operationId": "list_transcriptions", "summary": "List your transcription requests", - "description": "Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`.", + "description": "Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `eta_seconds` and `next_poll_seconds`.", "security": [ { "bearerAuth": [] @@ -22397,7 +22457,7 @@ ], "operationId": "get_transcription", "summary": "Poll transcription status", - "description": "Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up.", + "description": "Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `eta_seconds` and `next_poll_seconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and next_poll_seconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up.", "security": [ { "bearerAuth": [] @@ -22454,7 +22514,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TranscriptionRequest" + "$ref": "#/components/schemas/TranscriptionJob" } } } @@ -22487,7 +22547,7 @@ ], "operationId": "submit_correction", "summary": "Submit a transcript correction", - "description": "Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key.", + "description": "Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Every refusal uses the shared error envelope. Error semantics for outbox-style clients: a 409 revision_mismatch or anchor_mismatch means the transcript changed, and error.current_revision is the revision to re-read; create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence_mismatch stores no receipt or effect; error.expected_seq is the next seq, so synchronize local counters and resend under the same key.", "security": [ { "bearerAuth": [] @@ -22511,7 +22571,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once." } ], "requestBody": { @@ -22665,7 +22726,7 @@ } }, "412": { - "description": "Sequence mismatch ({ error, expectedSeq }). Refetch, rebase, resend.", + "description": "sequence_mismatch. error.expected_seq is the next seq for this video. Refetch, rebase, resend under the same key.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" @@ -22677,7 +22738,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CorrectionSeqMismatchResponse" + "$ref": "#/components/schemas/Error" } } } @@ -22941,7 +23002,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." } ], "requestBody": { @@ -23161,7 +23223,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." } ], "requestBody": { @@ -23383,7 +23446,8 @@ "required": false, "name": "Idempotency-Key", "in": "header", - "description": "Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." + "example": "8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", + "description": "1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced." } ], "requestBody": { diff --git a/reference.md b/reference.md index 8aa0782..ac43cf9 100644 --- a/reference.md +++ b/reference.md @@ -701,7 +701,7 @@ client.entities.momentum(
-Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. +Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.
@@ -996,7 +996,7 @@ client.mentions.count()
-Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. +Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated.
@@ -1180,6 +1180,7 @@ client = Arcmira( ) client.feedback.submit( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", type="recommendations", query={ "key": "value" @@ -1216,7 +1217,7 @@ client.feedback.submit(
-**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. +**idempotency_key:** `typing.Optional[str]` — 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced.
@@ -1531,7 +1532,7 @@ client.transcripts.search(
-Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. +Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. Branch on state: 200 ready is owned content; 202 pending carries the job, with Retry-After; 200 preparation_required carries the whole-video quote and the action to take, POST /v1/transcriptions with { video_id }, plus last_attempt when the previous purchase failed. Plans without Premium read captions with an access gate instead. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections.
@@ -1580,7 +1581,7 @@ client.transcripts.get(
-**quality:** `typing.Optional[GetTranscriptsRequestQuality]` — captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. +**quality:** `typing.Optional[GetTranscriptsRequestQuality]` — captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings.
@@ -1652,7 +1653,7 @@ client.transcripts.get(
-Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. +Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. A video with no known duration, or one past the 12 hour cap, answers 400 invalid_query with param video_id.
@@ -1798,7 +1799,7 @@ client.transcripts.captions(
-Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. +Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `eta_seconds` and `next_poll_seconds`.
@@ -1885,7 +1886,7 @@ client.transcripts.list_requests()
-Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. +Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it.
@@ -1909,8 +1910,7 @@ client = Arcmira( ) client.transcripts.request( - idempotency_key="Idempotency-Key", - max_rows=1, + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) ``` @@ -1927,7 +1927,7 @@ client.transcripts.request(
-**idempotency_key:** `str` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. +**idempotency_key:** `typing.Optional[str]` — 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced.
@@ -1935,7 +1935,7 @@ client.transcripts.request(
-**max_rows:** `int` — Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. +**video_id:** `typing.Optional[str]` — YouTube video id (11 characters). Either video_id or url is required.
@@ -1943,7 +1943,7 @@ client.transcripts.request(
-**max_on_demand_cents:** `typing.Optional[float]` — Maximum new monetary on-demand charge in cents. Omit to authorize none. +**url:** `typing.Optional[str]` — A YouTube watch/short/live URL. Either video_id or url is required.
@@ -1951,7 +1951,7 @@ client.transcripts.request(
-**video_id:** `typing.Optional[str]` — YouTube video id (11 characters). Either videoId or url is required. +**max_rows:** `typing.Optional[int]` — Maximum whole-video rows authorized. Credit mode charges four credits per row. Omit it to cap the purchase at the current quote. Required with max_on_demand_cents above 0.
@@ -1959,7 +1959,7 @@ client.transcripts.request(
-**url:** `typing.Optional[str]` — A YouTube watch/short/live URL. Either videoId or url is required. +**max_on_demand_cents:** `typing.Optional[int]` — Maximum new monetary on-demand charge in whole cents. Defaults to 0, which moves no money. Above 0 it requires Idempotency-Key and max_rows.
@@ -1979,7 +1979,7 @@ client.transcripts.request(
-
client.transcripts.status(...) -> TranscriptRequest +
client.transcripts.status(...) -> TranscriptJob
@@ -1991,7 +1991,7 @@ client.transcripts.request(
-Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. +Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `eta_seconds` and `next_poll_seconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and next_poll_seconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up.
@@ -2539,6 +2539,7 @@ client = Arcmira( ) client.monitors.create( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", name="name", ) @@ -2564,7 +2565,7 @@ client.monitors.create(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -2693,6 +2694,7 @@ client = Arcmira( client.monitors.delete( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) ``` @@ -2717,7 +2719,7 @@ client.monitors.delete(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -2774,6 +2776,7 @@ client = Arcmira( client.monitors.update( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) ``` @@ -2798,7 +2801,7 @@ client.monitors.update(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -2959,6 +2962,7 @@ client = Arcmira( client.monitors.rotate_webhook_secret( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) ``` @@ -2983,7 +2987,7 @@ client.monitors.rotate_webhook_secret(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -3103,6 +3107,7 @@ client = Arcmira( ) client.trackers.create( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", entity_name="entityName", entity_type="person", ) @@ -3137,7 +3142,7 @@ client.trackers.create(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -3266,6 +3271,7 @@ client = Arcmira( client.trackers.delete( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) ``` @@ -3290,7 +3296,7 @@ client.trackers.delete(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -3347,6 +3353,7 @@ client = Arcmira( client.trackers.update( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) ``` @@ -3371,7 +3378,7 @@ client.trackers.update(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -3611,7 +3618,7 @@ client.team.spend()
-Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. +Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Every refusal uses the shared error envelope. Error semantics for outbox-style clients: a 409 revision_mismatch or anchor_mismatch means the transcript changed, and error.current_revision is the revision to re-read; create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence_mismatch stores no receipt or effect; error.expected_seq is the next seq, so synchronize local counters and resend under the same key.
@@ -3636,6 +3643,7 @@ client = Arcmira( client.corrections.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", kind="line_edit", payload={ "key": "value" @@ -3680,7 +3688,7 @@ client.corrections.submit(
-**idempotency_key:** `typing.Optional[str]` — Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. +**idempotency_key:** `typing.Optional[str]` — 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once.
@@ -4012,7 +4020,7 @@ client.channels.sponsors.list(
-The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.
@@ -4069,7 +4077,7 @@ client.channels.videos.list(
-**cursor:** `typing.Optional[str]` — Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. +**cursor:** `typing.Optional[str]` — Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor.
@@ -4118,7 +4126,7 @@ client.channels.videos.list(
-The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -4175,7 +4183,7 @@ client.channels.related.topics(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -4255,7 +4263,7 @@ client.channels.related.topics(
-The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -4312,7 +4320,7 @@ client.channels.related.people(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -4392,7 +4400,7 @@ client.channels.related.people(
-The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -4449,7 +4457,7 @@ client.channels.related.organizations(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -4529,7 +4537,7 @@ client.channels.related.organizations(
-The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -4586,7 +4594,7 @@ client.channels.related.products(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -4666,7 +4674,7 @@ client.channels.related.products(
-The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -4723,7 +4731,7 @@ client.channels.related.channels(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -4804,7 +4812,7 @@ client.channels.related.channels(
-People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -4861,7 +4869,7 @@ client.channels.guests.list(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -5096,7 +5104,7 @@ client.entities.mentions.list(
-Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. +Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded.
@@ -5326,6 +5334,7 @@ client = Arcmira( client.monitors.trackers.add( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", tracker_ids=[ "trackerIds" ], @@ -5361,7 +5370,7 @@ client.monitors.trackers.add(
-**idempotency_key:** `typing.Optional[str]` — Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. +**idempotency_key:** `typing.Optional[str]` — One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation.
@@ -5394,7 +5403,7 @@ client.monitors.trackers.add(
-The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. +The newest limit alert deliveries for the monitor (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.
@@ -5443,7 +5452,7 @@ client.monitors.alerts.list(
-**n:** `typing.Optional[int]` +**limit:** `typing.Optional[int]` — Alerts to return, newest first, 1 to 100. Default 25.
@@ -5476,7 +5485,7 @@ client.monitors.alerts.list(
-The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -5533,7 +5542,7 @@ client.organizations.related.topics(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -5613,7 +5622,7 @@ client.organizations.related.topics(
-The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -5670,7 +5679,7 @@ client.organizations.related.people(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -5750,7 +5759,7 @@ client.organizations.related.people(
-The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -5807,7 +5816,7 @@ client.organizations.related.organizations(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -5887,7 +5896,7 @@ client.organizations.related.organizations(
-The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -5944,7 +5953,7 @@ client.organizations.related.products(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6024,7 +6033,7 @@ client.organizations.related.products(
-The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -6081,7 +6090,7 @@ client.organizations.related.channels(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6162,7 +6171,7 @@ client.organizations.related.channels(
-Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. +Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead.
@@ -6219,7 +6228,7 @@ client.people.appearances.list(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6300,7 +6309,7 @@ client.people.appearances.list(
-The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -6357,7 +6366,7 @@ client.people.related.topics(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6437,7 +6446,7 @@ client.people.related.topics(
-The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -6494,7 +6503,7 @@ client.people.related.people(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6574,7 +6583,7 @@ client.people.related.people(
-The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -6631,7 +6640,7 @@ client.people.related.organizations(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6711,7 +6720,7 @@ client.people.related.organizations(
-The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -6768,7 +6777,7 @@ client.people.related.products(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6848,7 +6857,7 @@ client.people.related.products(
-The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -6905,7 +6914,7 @@ client.people.related.channels(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -6986,7 +6995,7 @@ client.people.related.channels(
-The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7043,7 +7052,7 @@ client.products.related.topics(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -7123,7 +7132,7 @@ client.products.related.topics(
-The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7180,7 +7189,7 @@ client.products.related.people(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -7260,7 +7269,7 @@ client.products.related.people(
-The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7317,7 +7326,7 @@ client.products.related.organizations(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -7397,7 +7406,7 @@ client.products.related.organizations(
-The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7454,7 +7463,7 @@ client.products.related.products(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -7534,7 +7543,7 @@ client.products.related.products(
-The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7591,7 +7600,7 @@ client.products.related.channels(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -7760,7 +7769,7 @@ client.team.usage_events.list()
-The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7817,7 +7826,7 @@ client.topics.related.topics(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -7897,7 +7906,7 @@ client.topics.related.topics(
-The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -7954,7 +7963,7 @@ client.topics.related.people(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -8034,7 +8043,7 @@ client.topics.related.people(
-The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -8091,7 +8100,7 @@ client.topics.related.organizations(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -8171,7 +8180,7 @@ client.topics.related.organizations(
-The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -8228,7 +8237,7 @@ client.topics.related.products(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -8308,7 +8317,7 @@ client.topics.related.products(
-The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. +The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied.
@@ -8365,7 +8374,7 @@ client.topics.related.channels(
-**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. +**cursor:** `typing.Optional[str]` — Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live.
@@ -8446,7 +8455,7 @@ client.topics.related.channels(
-The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. +The newest limit alert deliveries for the tracker (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert.
@@ -8495,7 +8504,7 @@ client.trackers.alerts.list(
-**n:** `typing.Optional[int]` +**limit:** `typing.Optional[int]` — Alerts to return, newest first, 1 to 100. Default 25.
@@ -8553,6 +8562,7 @@ client = Arcmira( client.transcripts.edits.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", segment_index=1, original_text="originalText", corrected_text="correctedText", @@ -8604,7 +8614,7 @@ client.transcripts.edits.submit(
-**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. +**idempotency_key:** `typing.Optional[str]` — 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced.
@@ -8738,6 +8748,7 @@ client = Arcmira( client.transcripts.speakers.identify( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", speaker_id=1, ) @@ -8771,7 +8782,7 @@ client.transcripts.speakers.identify(
-**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. +**idempotency_key:** `typing.Optional[str]` — 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced.
@@ -8994,6 +9005,7 @@ client = Arcmira( client.transcripts.merges.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", source_name="sourceName", target_entity_id=1, ) @@ -9036,7 +9048,7 @@ client.transcripts.merges.submit(
-**idempotency_key:** `typing.Optional[str]` — Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. +**idempotency_key:** `typing.Optional[str]` — 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced.
diff --git a/scripts/prepare-openapi.py b/scripts/prepare-openapi.py index e029821..365111b 100644 --- a/scripts/prepare-openapi.py +++ b/scripts/prepare-openapi.py @@ -31,7 +31,7 @@ def prepare(document, names): for path in ('/v1/openapi.json', '/v1/signups', '/v1/signups/verify', '/v1/search'): del doc['paths'][path] type_names = { - 'TranscriptionRequest': 'TranscriptRequest', + 'TranscriptionJob': 'TranscriptJob', 'TranscriptionSubmitResponse': 'TranscriptRequestSubmitResponse', 'TranscriptionListResponse': 'TranscriptRequestListResponse', } @@ -48,6 +48,17 @@ def rename_refs(value): elif isinstance(value, list): for child in value: rename_refs(child) rename_refs(doc) + def collapse_job_refs(value): + # Fern inlines an allOf of the Job $ref plus a description; a bare $ref keeps one shared Job type. + if isinstance(value, dict): + parts = value.get('allOf') + if parts and sum('$ref' in part for part in parts) == 1 and all('$ref' in part or set(part) == {'description'} for part in parts) and any(part.get('$ref') == '#/components/schemas/TranscriptJob' for part in parts): + del value['allOf'] + value['$ref'] = next(part['$ref'] for part in parts if '$ref' in part) + for child in value.values(): collapse_job_refs(child) + elif isinstance(value, list): + for child in value: collapse_job_refs(child) + collapse_job_refs(doc) # Fern 5.131.1 loses inherited example fields in this object intersection. suggestion = doc['components']['schemas']['ResolveSuggestion'] members = [resolve(doc, part) for part in suggestion.pop('allOf')] @@ -81,13 +92,17 @@ def rename_refs(value): op['parameters'] = [p for p in op.get('parameters', []) if not (p.get('in') == 'query' and p['name'] in {'type', 'query'})] body = op['requestBody']['content']['application/json']['schema'] body['required'] = sorted(set(body.get('required', [])) | {'type', 'query'}) + if op.get('operationId') == 'submit_transcription': + # videoId is a one-release alias of video_id and collides with it after camelCase normalization. + op['requestBody']['content']['application/json']['schema']['properties'].pop('videoId') responses = [] for code, response in op.get('responses', {}).items(): if code.startswith('2'): response = resolve(doc, response) schema = response.get('content', {}).get('application/json', {}).get('schema') - if schema is not None and schema not in responses: - responses.append(schema) + for member in (schema or {}).get('oneOf', [schema] if schema is not None else []): + if member not in responses: + responses.append(member) if len(responses) > 1: states = {} for schema in responses: diff --git a/src/arcmira/__init__.py b/src/arcmira/__init__.py index de0f351..2215184 100644 --- a/src/arcmira/__init__.py +++ b/src/arcmira/__init__.py @@ -63,7 +63,6 @@ ChannelVideosResponseEpisodesItem, CorrectionAcceptedResponse, CorrectionAcceptedResponseKind, - CorrectionSeqMismatchResponse, DeliveryIssueChange, DeliveryIssueChangeChannel, Entity, @@ -341,23 +340,34 @@ TranscriptEditSubmittedResponse, TranscriptEditSubmittedResponseEdit, TranscriptEditSubmittedResponseEditStatus, + TranscriptJob, + TranscriptJobCharge, + TranscriptJobChargeFrom, + TranscriptJobChargeUnit, + TranscriptJobStage, + TranscriptJobState, + TranscriptJobStatus, TranscriptPending, - TranscriptPendingPremiumJob, TranscriptPendingQuality, + TranscriptPreparationRequired, + TranscriptPreparationRequiredAction, + TranscriptPreparationRequiredActionBody, + TranscriptPreparationRequiredActionMethod, + TranscriptPreparationRequiredLastAttempt, + TranscriptPreparationRequiredQuality, + TranscriptPreparationRequiredQuote, + TranscriptPreparationRequiredQuoteCharge, + TranscriptPreparationRequiredQuoteChargeFrom, + TranscriptPreparationRequiredQuoteChargeUnit, TranscriptPurchaseQuote, TranscriptPurchaseQuoteBillingScope, TranscriptPurchaseQuoteCharge, + TranscriptPurchaseQuoteChargeFrom, TranscriptPurchaseQuoteChargeUnit, + TranscriptPurchaseQuoteUpgrade, TranscriptQuote, - TranscriptRequest, - TranscriptRequestCharge, - TranscriptRequestChargeUnit, TranscriptRequestListResponse, TranscriptRequestListResponseRequestsItem, - TranscriptRequestQuote, - TranscriptRequestStage, - TranscriptRequestState, - TranscriptRequestStatus, TranscriptRequestSubmitResponse, TranscriptResponse, TranscriptResponseAccess, @@ -368,13 +378,13 @@ TranscriptResponseAccessUnlockAction, TranscriptResponseLinesItem, TranscriptResponseParagraphsItem, - TranscriptResponsePremiumJob, TranscriptResponseQuality, TranscriptResponseRange, TranscriptResponseSource, TranscriptResponseSpeakersItem, TranscriptResult, TranscriptResult_Pending, + TranscriptResult_PreparationRequired, TranscriptResult_Ready, TranscriptSearchChunk, TranscriptSearchResponse, @@ -416,7 +426,6 @@ ServiceUnavailableError, TooManyRequestsError, UnauthorizedError, - UnprocessableEntityError, ) from . import ( channels, @@ -527,7 +536,6 @@ "ConflictError": ".errors", "CorrectionAcceptedResponse": ".types", "CorrectionAcceptedResponseKind": ".types", - "CorrectionSeqMismatchResponse": ".types", "CountMentionsRequestMode": ".mentions", "CreateMonitorsRequestNotifyFrequency": ".monitors", "CreateTrackersRequestEntityType": ".trackers", @@ -837,23 +845,34 @@ "TranscriptEditSubmittedResponse": ".types", "TranscriptEditSubmittedResponseEdit": ".types", "TranscriptEditSubmittedResponseEditStatus": ".types", + "TranscriptJob": ".types", + "TranscriptJobCharge": ".types", + "TranscriptJobChargeFrom": ".types", + "TranscriptJobChargeUnit": ".types", + "TranscriptJobStage": ".types", + "TranscriptJobState": ".types", + "TranscriptJobStatus": ".types", "TranscriptPending": ".types", - "TranscriptPendingPremiumJob": ".types", "TranscriptPendingQuality": ".types", + "TranscriptPreparationRequired": ".types", + "TranscriptPreparationRequiredAction": ".types", + "TranscriptPreparationRequiredActionBody": ".types", + "TranscriptPreparationRequiredActionMethod": ".types", + "TranscriptPreparationRequiredLastAttempt": ".types", + "TranscriptPreparationRequiredQuality": ".types", + "TranscriptPreparationRequiredQuote": ".types", + "TranscriptPreparationRequiredQuoteCharge": ".types", + "TranscriptPreparationRequiredQuoteChargeFrom": ".types", + "TranscriptPreparationRequiredQuoteChargeUnit": ".types", "TranscriptPurchaseQuote": ".types", "TranscriptPurchaseQuoteBillingScope": ".types", "TranscriptPurchaseQuoteCharge": ".types", + "TranscriptPurchaseQuoteChargeFrom": ".types", "TranscriptPurchaseQuoteChargeUnit": ".types", + "TranscriptPurchaseQuoteUpgrade": ".types", "TranscriptQuote": ".types", - "TranscriptRequest": ".types", - "TranscriptRequestCharge": ".types", - "TranscriptRequestChargeUnit": ".types", "TranscriptRequestListResponse": ".types", "TranscriptRequestListResponseRequestsItem": ".types", - "TranscriptRequestQuote": ".types", - "TranscriptRequestStage": ".types", - "TranscriptRequestState": ".types", - "TranscriptRequestStatus": ".types", "TranscriptRequestSubmitResponse": ".types", "TranscriptResponse": ".types", "TranscriptResponseAccess": ".types", @@ -864,13 +883,13 @@ "TranscriptResponseAccessUnlockAction": ".types", "TranscriptResponseLinesItem": ".types", "TranscriptResponseParagraphsItem": ".types", - "TranscriptResponsePremiumJob": ".types", "TranscriptResponseQuality": ".types", "TranscriptResponseRange": ".types", "TranscriptResponseSource": ".types", "TranscriptResponseSpeakersItem": ".types", "TranscriptResult": ".types", "TranscriptResult_Pending": ".types", + "TranscriptResult_PreparationRequired": ".types", "TranscriptResult_Ready": ".types", "TranscriptSearchChunk": ".types", "TranscriptSearchResponse": ".types", @@ -887,7 +906,6 @@ "TranscriptSettingsQuality": ".types", "TranscriptVideo": ".types", "UnauthorizedError": ".errors", - "UnprocessableEntityError": ".errors", "UpdateMonitorsRequestNotifyFrequency": ".monitors", "UpdateSettingsMeRequestTranscripts": ".me", "UpdateSettingsMeRequestTranscriptsQuality": ".me", @@ -1008,7 +1026,6 @@ def __dir__(): "ConflictError", "CorrectionAcceptedResponse", "CorrectionAcceptedResponseKind", - "CorrectionSeqMismatchResponse", "CountMentionsRequestMode", "CreateMonitorsRequestNotifyFrequency", "CreateTrackersRequestEntityType", @@ -1318,23 +1335,34 @@ def __dir__(): "TranscriptEditSubmittedResponse", "TranscriptEditSubmittedResponseEdit", "TranscriptEditSubmittedResponseEditStatus", + "TranscriptJob", + "TranscriptJobCharge", + "TranscriptJobChargeFrom", + "TranscriptJobChargeUnit", + "TranscriptJobStage", + "TranscriptJobState", + "TranscriptJobStatus", "TranscriptPending", - "TranscriptPendingPremiumJob", "TranscriptPendingQuality", + "TranscriptPreparationRequired", + "TranscriptPreparationRequiredAction", + "TranscriptPreparationRequiredActionBody", + "TranscriptPreparationRequiredActionMethod", + "TranscriptPreparationRequiredLastAttempt", + "TranscriptPreparationRequiredQuality", + "TranscriptPreparationRequiredQuote", + "TranscriptPreparationRequiredQuoteCharge", + "TranscriptPreparationRequiredQuoteChargeFrom", + "TranscriptPreparationRequiredQuoteChargeUnit", "TranscriptPurchaseQuote", "TranscriptPurchaseQuoteBillingScope", "TranscriptPurchaseQuoteCharge", + "TranscriptPurchaseQuoteChargeFrom", "TranscriptPurchaseQuoteChargeUnit", + "TranscriptPurchaseQuoteUpgrade", "TranscriptQuote", - "TranscriptRequest", - "TranscriptRequestCharge", - "TranscriptRequestChargeUnit", "TranscriptRequestListResponse", "TranscriptRequestListResponseRequestsItem", - "TranscriptRequestQuote", - "TranscriptRequestStage", - "TranscriptRequestState", - "TranscriptRequestStatus", "TranscriptRequestSubmitResponse", "TranscriptResponse", "TranscriptResponseAccess", @@ -1345,13 +1373,13 @@ def __dir__(): "TranscriptResponseAccessUnlockAction", "TranscriptResponseLinesItem", "TranscriptResponseParagraphsItem", - "TranscriptResponsePremiumJob", "TranscriptResponseQuality", "TranscriptResponseRange", "TranscriptResponseSource", "TranscriptResponseSpeakersItem", "TranscriptResult", "TranscriptResult_Pending", + "TranscriptResult_PreparationRequired", "TranscriptResult_Ready", "TranscriptSearchChunk", "TranscriptSearchResponse", @@ -1368,7 +1396,6 @@ def __dir__(): "TranscriptSettingsQuality", "TranscriptVideo", "UnauthorizedError", - "UnprocessableEntityError", "UpdateMonitorsRequestNotifyFrequency", "UpdateSettingsMeRequestTranscripts", "UpdateSettingsMeRequestTranscriptsQuality", diff --git a/src/arcmira/channels/guests/client.py b/src/arcmira/channels/guests/client.py index cf8620e..0fe3e45 100644 --- a/src/arcmira/channels/guests/client.py +++ b/src/arcmira/channels/guests/client.py @@ -43,7 +43,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: """ - People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -53,7 +53,7 @@ def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -141,7 +141,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: """ - People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -151,7 +151,7 @@ async def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/channels/guests/raw_client.py b/src/arcmira/channels/guests/raw_client.py index 4337289..15a5ad9 100644 --- a/src/arcmira/channels/guests/raw_client.py +++ b/src/arcmira/channels/guests/raw_client.py @@ -45,7 +45,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: """ - People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -55,7 +55,7 @@ def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -229,7 +229,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelGuestListResponseItemsItem, ChannelGuestListResponse]: """ - People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + People who appeared as guests on the channel, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -239,7 +239,7 @@ async def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/channels/related/client.py b/src/arcmira/channels/related/client.py index af626a3..7d19985 100644 --- a/src/arcmira/channels/related/client.py +++ b/src/arcmira/channels/related/client.py @@ -63,7 +63,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -73,7 +73,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -145,7 +145,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -155,7 +155,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -227,7 +227,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -237,7 +237,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -309,7 +309,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -319,7 +319,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -391,7 +391,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -401,7 +401,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -489,7 +489,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -499,7 +499,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -580,7 +580,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -590,7 +590,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -671,7 +671,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -681,7 +681,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -762,7 +762,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -772,7 +772,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -853,7 +853,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -863,7 +863,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/channels/related/raw_client.py b/src/arcmira/channels/related/raw_client.py index b765479..8f405ab 100644 --- a/src/arcmira/channels/related/raw_client.py +++ b/src/arcmira/channels/related/raw_client.py @@ -65,7 +65,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -75,7 +75,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -244,7 +244,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -254,7 +254,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -423,7 +423,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -433,7 +433,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -602,7 +602,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -612,7 +612,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -781,7 +781,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -791,7 +791,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -965,7 +965,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -975,7 +975,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1147,7 +1147,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1157,7 +1157,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1329,7 +1329,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1339,7 +1339,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1511,7 +1511,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1521,7 +1521,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1693,7 +1693,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this channel in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1703,7 +1703,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/channels/videos/client.py b/src/arcmira/channels/videos/client.py index 5c5fb3d..0d81a1f 100644 --- a/src/arcmira/channels/videos/client.py +++ b/src/arcmira/channels/videos/client.py @@ -36,7 +36,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -47,7 +47,7 @@ def list( Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. cursor : typing.Optional[str] - Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor. published_after : typing.Optional[str] ISO date. Only videos published on or after this day. @@ -115,7 +115,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -126,7 +126,7 @@ async def list( Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. cursor : typing.Optional[str] - Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor. published_after : typing.Optional[str] ISO date. Only videos published on or after this day. diff --git a/src/arcmira/channels/videos/raw_client.py b/src/arcmira/channels/videos/raw_client.py index 30be0cc..ae833d0 100644 --- a/src/arcmira/channels/videos/raw_client.py +++ b/src/arcmira/channels/videos/raw_client.py @@ -38,7 +38,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -49,7 +49,7 @@ def list( Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. cursor : typing.Optional[str] - Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor. published_after : typing.Optional[str] ISO date. Only videos published on or after this day. @@ -199,7 +199,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing rows remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Bills one row per video returned. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -210,7 +210,7 @@ async def list( Videos to return, 1 to 25, newest first. Default 10. Pass 1 for the latest episode. cursor : typing.Optional[str] - Opaque continuation from next_cursor. Bound to the channel, filters, limit, caller, and visibility. Invalid or old tokens return invalid_cursor. + Opaque continuation from next_cursor. Bound to the channel, filters, caller, and visibility; limit may change between pages. Invalid or old tokens return invalid_cursor. published_after : typing.Optional[str] ISO date. Only videos published on or after this day. diff --git a/src/arcmira/corrections/client.py b/src/arcmira/corrections/client.py index 75e6e22..7c7cd7f 100644 --- a/src/arcmira/corrections/client.py +++ b/src/arcmira/corrections/client.py @@ -42,7 +42,7 @@ def submit( request_options: typing.Optional[RequestOptions] = None, ) -> CorrectionAcceptedResponse: """ - Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Every refusal uses the shared error envelope. Error semantics for outbox-style clients: a 409 revision_mismatch or anchor_mismatch means the transcript changed, and error.current_revision is the revision to re-read; create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence_mismatch stores no receipt or effect; error.expected_seq is the next seq, so synchronize local counters and resend under the same key. Parameters ---------- @@ -55,7 +55,7 @@ def submit( Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. idempotency_key : typing.Optional[str] - Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. seq : typing.Optional[int] Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. @@ -83,6 +83,7 @@ def submit( ) client.corrections.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", kind="line_edit", payload={"key": "value"}, ) @@ -221,7 +222,7 @@ async def submit( request_options: typing.Optional[RequestOptions] = None, ) -> CorrectionAcceptedResponse: """ - Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Every refusal uses the shared error envelope. Error semantics for outbox-style clients: a 409 revision_mismatch or anchor_mismatch means the transcript changed, and error.current_revision is the revision to re-read; create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence_mismatch stores no receipt or effect; error.expected_seq is the next seq, so synchronize local counters and resend under the same key. Parameters ---------- @@ -234,7 +235,7 @@ async def submit( Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. idempotency_key : typing.Optional[str] - Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. seq : typing.Optional[int] Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. @@ -267,6 +268,7 @@ async def submit( async def main() -> None: await client.corrections.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", kind="line_edit", payload={"key": "value"}, ) diff --git a/src/arcmira/corrections/raw_client.py b/src/arcmira/corrections/raw_client.py index 50351d4..7b19b7a 100644 --- a/src/arcmira/corrections/raw_client.py +++ b/src/arcmira/corrections/raw_client.py @@ -20,7 +20,6 @@ from ..errors.too_many_requests_error import TooManyRequestsError from ..errors.unauthorized_error import UnauthorizedError from ..types.correction_accepted_response import CorrectionAcceptedResponse -from ..types.correction_seq_mismatch_response import CorrectionSeqMismatchResponse from ..types.error import Error from ..types.withdrawn_response import WithdrawnResponse from .types.submit_corrections_request_anchor import SubmitCorrectionsRequestAnchor @@ -48,7 +47,7 @@ def submit( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[CorrectionAcceptedResponse]: """ - Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Every refusal uses the shared error envelope. Error semantics for outbox-style clients: a 409 revision_mismatch or anchor_mismatch means the transcript changed, and error.current_revision is the revision to re-read; create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence_mismatch stores no receipt or effect; error.expected_seq is the next seq, so synchronize local counters and resend under the same key. Parameters ---------- @@ -61,7 +60,7 @@ def submit( Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. idempotency_key : typing.Optional[str] - Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. seq : typing.Optional[int] Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. @@ -168,9 +167,9 @@ def submit( raise PreconditionFailedError( headers=dict(_response.headers), body=typing.cast( - CorrectionSeqMismatchResponse, + Error, parse_obj_as( - type_=CorrectionSeqMismatchResponse, # type: ignore + type_=Error, # type: ignore object_=_response.json(), ), ), @@ -545,7 +544,7 @@ async def submit( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[CorrectionAcceptedResponse]: """ - Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Error semantics for outbox-style clients: A 409 with a revision/anchor reason means the transcript changed (body { reason, currentRevision }); create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 with reason idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence mismatch (body { expectedSeq }) stores no receipt or effect; synchronize local counters and resend under the same key. + Unified corrections ingestion for all kinds: line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite. Corrections are free (0 rows) and land as pending-review rows attributed to your API key; speaker_identify/add_person also create a community-flagged appearance immediately. segment_rewrite is the structural primitive: it replaces an inclusive segment range with new segments (an empty replacements array deletes the range); timestamps can be pinned per replacement with optional start/end seconds, and unpinned times are repaired by char-proportional interpolation between pins. Anchored kinds (line_edit, speaker_reassign, entity_tag, segment_rewrite) must echo the `revision` from a Premium transcript read and an `anchor` ({ segmentIndex, contentHash: djb2 of the covered segment text }); `anchor.segmentIndex` is the line's `index` in that read. Every refusal uses the shared error envelope. Error semantics for outbox-style clients: a 409 revision_mismatch or anchor_mismatch means the transcript changed, and error.current_revision is the revision to re-read; create a new event and key after re-anchoring, because the original refusal consumed its sequence and is replayable. A 409 idempotency_conflict means a finalized key was reused for changed intent; recover the original request instead of rebasing that key. A 412 sequence_mismatch stores no receipt or effect; error.expected_seq is the next seq, so synchronize local counters and resend under the same key. Parameters ---------- @@ -558,7 +557,7 @@ async def submit( Kind-specific payload. line_edit: { segmentIndex, originalText, correctedText }. speaker_reassign: { selection: { startIndex, startChar, endIndex, endChar }, target: { kind: existing|new|role, speakerId, label?, role? } }. speaker_identify: { speakerId, entityId }. add_person: { speakerId, name }. entity_tag: { segmentIndex, charStart, charEnd, entityId } or { segmentIndex, charStart, charEnd, proposedName, proposedType }. segment_rewrite: { startIndex, endIndex, replacements: [{ text, speaker?, start?, end? }] }. replaces the inclusive segment range with the replacements (max 50 source segments, 50 replacements, 2000 chars each); an empty array deletes the range. Timestamps: optional start/end pins (seconds, non-decreasing across the list) fix times explicitly; every unpinned time is interpolated char-proportionally between the surrounding pins (outer bounds default to the source time range). A replacement without speaker inherits from the source segment its repaired start time falls in; startIndex must equal anchor.segmentIndex and the anchor contentHash covers startIndex through endIndex. idempotency_key : typing.Optional[str] - Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique client event key with the HTTP method, public path and parsed body. The same finalized intent replays its original response with Idempotency-Replayed: true; changed finalized input returns 409 idempotency_conflict. Receipts have no expiry. A 412 stores no receipt or effect, so synchronize the sequence and resend under the same key. Other finalized refusals consume the eligible sequence once. seq : typing.Optional[int] Per-video monotonic sequence number (strict FIFO per user+video). Optional for one-off submissions; required for outbox-style clients that depend on ordering. Any mismatch returns 412 with the expected value. @@ -665,9 +664,9 @@ async def submit( raise PreconditionFailedError( headers=dict(_response.headers), body=typing.cast( - CorrectionSeqMismatchResponse, + Error, parse_obj_as( - type_=CorrectionSeqMismatchResponse, # type: ignore + type_=Error, # type: ignore object_=_response.json(), ), ), diff --git a/src/arcmira/entities/recommendations/client.py b/src/arcmira/entities/recommendations/client.py index ea9a9fe..25fc62c 100644 --- a/src/arcmira/entities/recommendations/client.py +++ b/src/arcmira/entities/recommendations/client.py @@ -42,7 +42,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. Parameters ---------- @@ -138,7 +138,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. Parameters ---------- diff --git a/src/arcmira/entities/recommendations/raw_client.py b/src/arcmira/entities/recommendations/raw_client.py index e6befdf..6815d72 100644 --- a/src/arcmira/entities/recommendations/raw_client.py +++ b/src/arcmira/entities/recommendations/raw_client.py @@ -44,7 +44,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. Parameters ---------- @@ -227,7 +227,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) for one entity, newest media first. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Rows below min_confidence (default 0.7) and disputed rows (unless include_disputed=true) are excluded. Parameters ---------- diff --git a/src/arcmira/errors/__init__.py b/src/arcmira/errors/__init__.py index 4218461..1c9d8b3 100644 --- a/src/arcmira/errors/__init__.py +++ b/src/arcmira/errors/__init__.py @@ -16,7 +16,6 @@ from .service_unavailable_error import ServiceUnavailableError from .too_many_requests_error import TooManyRequestsError from .unauthorized_error import UnauthorizedError - from .unprocessable_entity_error import UnprocessableEntityError _dynamic_imports: typing.Dict[str, str] = { "BadRequestError": ".bad_request_error", "ConflictError": ".conflict_error", @@ -28,7 +27,6 @@ "ServiceUnavailableError": ".service_unavailable_error", "TooManyRequestsError": ".too_many_requests_error", "UnauthorizedError": ".unauthorized_error", - "UnprocessableEntityError": ".unprocessable_entity_error", } @@ -64,5 +62,4 @@ def __dir__(): "ServiceUnavailableError", "TooManyRequestsError", "UnauthorizedError", - "UnprocessableEntityError", ] diff --git a/src/arcmira/errors/precondition_failed_error.py b/src/arcmira/errors/precondition_failed_error.py index 0a8cf48..0c41f0b 100644 --- a/src/arcmira/errors/precondition_failed_error.py +++ b/src/arcmira/errors/precondition_failed_error.py @@ -3,9 +3,9 @@ import typing from ..core.api_error import ApiError -from ..types.correction_seq_mismatch_response import CorrectionSeqMismatchResponse +from ..types.error import Error class PreconditionFailedError(ApiError): - def __init__(self, body: CorrectionSeqMismatchResponse, headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=412, headers=headers, body=body) diff --git a/src/arcmira/errors/unprocessable_entity_error.py b/src/arcmira/errors/unprocessable_entity_error.py deleted file mode 100644 index 31587f1..0000000 --- a/src/arcmira/errors/unprocessable_entity_error.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ..core.api_error import ApiError -from ..types.error import Error - - -class UnprocessableEntityError(ApiError): - def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None): - super().__init__(status_code=422, headers=headers, body=body) diff --git a/src/arcmira/feedback/client.py b/src/arcmira/feedback/client.py index 404dbe5..5fa15c3 100644 --- a/src/arcmira/feedback/client.py +++ b/src/arcmira/feedback/client.py @@ -57,7 +57,7 @@ def submit( The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. endpoint : typing.Optional[str] @@ -89,6 +89,7 @@ def submit( api_key="YOUR_API_KEY", ) client.feedback.submit( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", type="recommendations", query={"key": "value"}, ) @@ -184,7 +185,7 @@ async def submit( The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. endpoint : typing.Optional[str] @@ -221,6 +222,7 @@ async def submit( async def main() -> None: await client.feedback.submit( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", type="recommendations", query={"key": "value"}, ) diff --git a/src/arcmira/feedback/raw_client.py b/src/arcmira/feedback/raw_client.py index 95d7274..6cbf5a7 100644 --- a/src/arcmira/feedback/raw_client.py +++ b/src/arcmira/feedback/raw_client.py @@ -61,7 +61,7 @@ def submit( The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. endpoint : typing.Optional[str] @@ -347,7 +347,7 @@ async def submit( The query object that produced the result you are reviewing, echoed back verbatim so reviewers can replay it. For monitor_alert feedback, carry monitor_id and/or tracker_id and/or alert_id. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. endpoint : typing.Optional[str] diff --git a/src/arcmira/mentions/client.py b/src/arcmira/mentions/client.py index c799b6f..221d1bb 100644 --- a/src/arcmira/mentions/client.py +++ b/src/arcmira/mentions/client.py @@ -49,7 +49,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- @@ -226,7 +226,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- diff --git a/src/arcmira/mentions/raw_client.py b/src/arcmira/mentions/raw_client.py index e6f7224..f90dd51 100644 --- a/src/arcmira/mentions/raw_client.py +++ b/src/arcmira/mentions/raw_client.py @@ -51,7 +51,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- @@ -410,7 +410,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Cursor-paginated mentions filtered by entity (entity_id or entity_name is required), channel, text query, sentiment, appearance flag, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Read timestamps from start_seconds / end_seconds (integer seconds; 0 means full episode); the MM:SS (or HH:MM:SS) string fields are deprecated. is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- diff --git a/src/arcmira/monitors/alerts/client.py b/src/arcmira/monitors/alerts/client.py index 688427c..b51b4a9 100644 --- a/src/arcmira/monitors/alerts/client.py +++ b/src/arcmira/monitors/alerts/client.py @@ -24,17 +24,18 @@ def with_raw_response(self) -> RawAlertsClient: return self._raw_client def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AlertListResponse: """ - The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the monitor (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Monitor id. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -55,7 +56,7 @@ def list( id="id", ) """ - _response = self._raw_client.list(id, n=n, request_options=request_options) + _response = self._raw_client.list(id, limit=limit, request_options=request_options) return _response.data @@ -75,17 +76,18 @@ def with_raw_response(self) -> AsyncRawAlertsClient: return self._raw_client async def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AlertListResponse: """ - The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the monitor (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Monitor id. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -114,5 +116,5 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.list(id, n=n, request_options=request_options) + _response = await self._raw_client.list(id, limit=limit, request_options=request_options) return _response.data diff --git a/src/arcmira/monitors/alerts/raw_client.py b/src/arcmira/monitors/alerts/raw_client.py index 1493b64..515fe50 100644 --- a/src/arcmira/monitors/alerts/raw_client.py +++ b/src/arcmira/monitors/alerts/raw_client.py @@ -26,17 +26,18 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[AlertListResponse]: """ - The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the monitor (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Monitor id. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -50,7 +51,7 @@ def list( f"v1/monitors/{encode_path_param(id)}/alerts", method="GET", params={ - "n": n, + "limit": limit, }, request_options=request_options, ) @@ -145,17 +146,18 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[AlertListResponse]: """ - The newest n alert deliveries for the monitor, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the monitor (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Monitor id. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -169,7 +171,7 @@ async def list( f"v1/monitors/{encode_path_param(id)}/alerts", method="GET", params={ - "n": n, + "limit": limit, }, request_options=request_options, ) diff --git a/src/arcmira/monitors/client.py b/src/arcmira/monitors/client.py index 7854299..9d76dcd 100644 --- a/src/arcmira/monitors/client.py +++ b/src/arcmira/monitors/client.py @@ -90,7 +90,7 @@ def create( Display name (1-100 characters). Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. notify_emails : typing.Optional[typing.Sequence[str]] Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. @@ -135,6 +135,7 @@ def create( api_key="YOUR_API_KEY", ) client.monitors.create( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", name="name", ) """ @@ -170,7 +171,7 @@ def delete( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -189,6 +190,7 @@ def delete( ) client.monitors.delete( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) """ _response = self._raw_client.delete(id, idempotency_key=idempotency_key, request_options=request_options) @@ -223,7 +225,7 @@ def update( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. name : typing.Optional[str] Display name (1-100 characters). Required on create. @@ -281,6 +283,7 @@ def update( ) client.monitors.update( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) """ _response = self._raw_client.update( @@ -319,7 +322,7 @@ def rotate_webhook_secret( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -338,6 +341,7 @@ def rotate_webhook_secret( ) client.monitors.rotate_webhook_secret( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) """ _response = self._raw_client.rotate_webhook_secret( @@ -439,7 +443,7 @@ async def create( Display name (1-100 characters). Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. notify_emails : typing.Optional[typing.Sequence[str]] Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. @@ -489,6 +493,7 @@ async def create( async def main() -> None: await client.monitors.create( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", name="name", ) @@ -527,7 +532,7 @@ async def delete( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -551,6 +556,7 @@ async def delete( async def main() -> None: await client.monitors.delete( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) @@ -588,7 +594,7 @@ async def update( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. name : typing.Optional[str] Display name (1-100 characters). Required on create. @@ -651,6 +657,7 @@ async def update( async def main() -> None: await client.monitors.update( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) @@ -692,7 +699,7 @@ async def rotate_webhook_secret( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -716,6 +723,7 @@ async def rotate_webhook_secret( async def main() -> None: await client.monitors.rotate_webhook_secret( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) diff --git a/src/arcmira/monitors/raw_client.py b/src/arcmira/monitors/raw_client.py index 1052b5f..b82b9a5 100644 --- a/src/arcmira/monitors/raw_client.py +++ b/src/arcmira/monitors/raw_client.py @@ -163,7 +163,7 @@ def create( Display name (1-100 characters). Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. notify_emails : typing.Optional[typing.Sequence[str]] Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. @@ -334,7 +334,7 @@ def delete( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -477,7 +477,7 @@ def update( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. name : typing.Optional[str] Display name (1-100 characters). Required on create. @@ -663,7 +663,7 @@ def rotate_webhook_secret( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -913,7 +913,7 @@ async def create( Display name (1-100 characters). Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. notify_emails : typing.Optional[typing.Sequence[str]] Desired email recipients. External recipients must confirm before delivery. Free includes one additional recipient per monitor; paid plans allow up to 20 total. Default []. @@ -1084,7 +1084,7 @@ async def delete( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1227,7 +1227,7 @@ async def update( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. name : typing.Optional[str] Display name (1-100 characters). Required on create. @@ -1413,7 +1413,7 @@ async def rotate_webhook_secret( Monitor id. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/src/arcmira/monitors/trackers/client.py b/src/arcmira/monitors/trackers/client.py index 84e4288..11f523a 100644 --- a/src/arcmira/monitors/trackers/client.py +++ b/src/arcmira/monitors/trackers/client.py @@ -76,7 +76,7 @@ def add( Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -95,6 +95,7 @@ def add( ) client.monitors.trackers.add( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", tracker_ids=["trackerIds"], ) """ @@ -178,7 +179,7 @@ async def add( Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -202,6 +203,7 @@ async def add( async def main() -> None: await client.monitors.trackers.add( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", tracker_ids=["trackerIds"], ) diff --git a/src/arcmira/monitors/trackers/raw_client.py b/src/arcmira/monitors/trackers/raw_client.py index bb09d00..62073b2 100644 --- a/src/arcmira/monitors/trackers/raw_client.py +++ b/src/arcmira/monitors/trackers/raw_client.py @@ -157,7 +157,7 @@ def add( Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -408,7 +408,7 @@ async def add( Ids of existing trackers ("trk_...") to attach to this monitor. Create trackers first via POST /v1/trackers. At most 90 IDs per request; duplicates count once. All IDs must belong to the account or none are attached. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/src/arcmira/organizations/related/client.py b/src/arcmira/organizations/related/client.py index 12f1ce2..a48fe3e 100644 --- a/src/arcmira/organizations/related/client.py +++ b/src/arcmira/organizations/related/client.py @@ -63,7 +63,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -73,7 +73,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -145,7 +145,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -155,7 +155,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -227,7 +227,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -237,7 +237,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -309,7 +309,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -319,7 +319,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -391,7 +391,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -401,7 +401,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -489,7 +489,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -499,7 +499,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -580,7 +580,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -590,7 +590,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -671,7 +671,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -681,7 +681,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -762,7 +762,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -772,7 +772,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -853,7 +853,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -863,7 +863,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/organizations/related/raw_client.py b/src/arcmira/organizations/related/raw_client.py index 0e34220..22a2ccb 100644 --- a/src/arcmira/organizations/related/raw_client.py +++ b/src/arcmira/organizations/related/raw_client.py @@ -65,7 +65,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -75,7 +75,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -244,7 +244,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -254,7 +254,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -423,7 +423,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -433,7 +433,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -602,7 +602,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -612,7 +612,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -781,7 +781,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -791,7 +791,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -965,7 +965,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -975,7 +975,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1147,7 +1147,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1157,7 +1157,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1329,7 +1329,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1339,7 +1339,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1511,7 +1511,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1521,7 +1521,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1693,7 +1693,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this organization in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1703,7 +1703,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/people/appearances/client.py b/src/arcmira/people/appearances/client.py index a8f6861..a686de6 100644 --- a/src/arcmira/people/appearances/client.py +++ b/src/arcmira/people/appearances/client.py @@ -43,7 +43,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: """ - Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. Parameters ---------- @@ -53,7 +53,7 @@ def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -141,7 +141,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: """ - Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. Parameters ---------- @@ -151,7 +151,7 @@ async def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/people/appearances/raw_client.py b/src/arcmira/people/appearances/raw_client.py index dd2bedd..b60c269 100644 --- a/src/arcmira/people/appearances/raw_client.py +++ b/src/arcmira/people/appearances/raw_client.py @@ -45,7 +45,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: """ - Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. Parameters ---------- @@ -55,7 +55,7 @@ def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -229,7 +229,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[PersonAppearanceListResponseItemsItem, PersonAppearanceListResponse]: """ - Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. + Appearances (the person was actually present in the media) for one person, newest first. Person-only: the equivalent route for any other entity type returns a 400 (appearances_person_only). Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. The rows are display-oriented. For programmatic pagination, date filtering, and the standard mention-row shape, use GET /v1/mentions?entity_id=...&is_appearance=true instead. Parameters ---------- @@ -239,7 +239,7 @@ async def list( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/people/related/client.py b/src/arcmira/people/related/client.py index 0a54cb2..1582b2e 100644 --- a/src/arcmira/people/related/client.py +++ b/src/arcmira/people/related/client.py @@ -63,7 +63,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -73,7 +73,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -145,7 +145,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -155,7 +155,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -227,7 +227,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -237,7 +237,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -309,7 +309,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -319,7 +319,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -391,7 +391,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -401,7 +401,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -489,7 +489,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -499,7 +499,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -580,7 +580,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -590,7 +590,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -671,7 +671,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -681,7 +681,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -762,7 +762,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -772,7 +772,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -853,7 +853,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -863,7 +863,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/people/related/raw_client.py b/src/arcmira/people/related/raw_client.py index 8be7530..d10b347 100644 --- a/src/arcmira/people/related/raw_client.py +++ b/src/arcmira/people/related/raw_client.py @@ -65,7 +65,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -75,7 +75,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -244,7 +244,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -254,7 +254,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -423,7 +423,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -433,7 +433,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -602,7 +602,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -612,7 +612,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -781,7 +781,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -791,7 +791,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -965,7 +965,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -975,7 +975,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1147,7 +1147,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1157,7 +1157,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1329,7 +1329,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1339,7 +1339,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1511,7 +1511,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1521,7 +1521,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1693,7 +1693,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this person in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1703,7 +1703,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/products/related/client.py b/src/arcmira/products/related/client.py index c249e54..2f83cd1 100644 --- a/src/arcmira/products/related/client.py +++ b/src/arcmira/products/related/client.py @@ -63,7 +63,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -73,7 +73,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -145,7 +145,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -155,7 +155,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -227,7 +227,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -237,7 +237,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -309,7 +309,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -319,7 +319,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -391,7 +391,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -401,7 +401,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -489,7 +489,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -499,7 +499,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -580,7 +580,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -590,7 +590,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -671,7 +671,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -681,7 +681,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -762,7 +762,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -772,7 +772,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -853,7 +853,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -863,7 +863,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/products/related/raw_client.py b/src/arcmira/products/related/raw_client.py index 7a8d1fb..5b482e7 100644 --- a/src/arcmira/products/related/raw_client.py +++ b/src/arcmira/products/related/raw_client.py @@ -65,7 +65,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -75,7 +75,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -244,7 +244,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -254,7 +254,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -423,7 +423,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -433,7 +433,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -602,7 +602,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -612,7 +612,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -781,7 +781,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -791,7 +791,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -965,7 +965,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -975,7 +975,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1147,7 +1147,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1157,7 +1157,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1329,7 +1329,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1339,7 +1339,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1511,7 +1511,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1521,7 +1521,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1693,7 +1693,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this product in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1703,7 +1703,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/recommendations/client.py b/src/arcmira/recommendations/client.py index c7bfd01..ea369fd 100644 --- a/src/arcmira/recommendations/client.py +++ b/src/arcmira/recommendations/client.py @@ -45,7 +45,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Parameters ---------- @@ -146,7 +146,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Parameters ---------- diff --git a/src/arcmira/recommendations/raw_client.py b/src/arcmira/recommendations/raw_client.py index 2bf77c2..ef6348b 100644 --- a/src/arcmira/recommendations/raw_client.py +++ b/src/arcmira/recommendations/raw_client.py @@ -46,7 +46,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Parameters ---------- @@ -239,7 +239,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Recommendation, RecommendationListResponse]: """ - Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, limit, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. + Cursor-paginated commercial mentions (ad reads, endorsements, neutral mentions) filtered by entity (entity_id or entity_name is required), channel, mention_class, confidence, and date range. The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing rows remain live. Requires a Pro+ plan. Read timestamps from start_seconds / end_seconds (integer seconds); the MM:SS string fields are deprecated. Parameters ---------- diff --git a/src/arcmira/topics/related/client.py b/src/arcmira/topics/related/client.py index 9af6a28..802abf3 100644 --- a/src/arcmira/topics/related/client.py +++ b/src/arcmira/topics/related/client.py @@ -63,7 +63,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -73,7 +73,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -145,7 +145,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -155,7 +155,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -227,7 +227,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -237,7 +237,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -309,7 +309,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -319,7 +319,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -391,7 +391,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -401,7 +401,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -489,7 +489,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -499,7 +499,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -580,7 +580,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -590,7 +590,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -671,7 +671,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -681,7 +681,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -762,7 +762,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -772,7 +772,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -853,7 +853,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -863,7 +863,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/topics/related/raw_client.py b/src/arcmira/topics/related/raw_client.py index 1511f2a..10e1f27 100644 --- a/src/arcmira/topics/related/raw_client.py +++ b/src/arcmira/topics/related/raw_client.py @@ -65,7 +65,7 @@ def topics( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -75,7 +75,7 @@ def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -244,7 +244,7 @@ def people( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -254,7 +254,7 @@ def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -423,7 +423,7 @@ def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -433,7 +433,7 @@ def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -602,7 +602,7 @@ def products( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -612,7 +612,7 @@ def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -781,7 +781,7 @@ def channels( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -791,7 +791,7 @@ def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -965,7 +965,7 @@ async def topics( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityTopicListResponseItemsItem, EntityTopicListResponse]: """ - The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The topics that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -975,7 +975,7 @@ async def topics( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1147,7 +1147,7 @@ async def people( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityPeopleListResponseItemsItem, EntityPeopleListResponse]: """ - The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The people that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1157,7 +1157,7 @@ async def people( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1329,7 +1329,7 @@ async def organizations( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityOrganizationListResponseItemsItem, EntityOrganizationListResponse]: """ - The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The organizations that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1339,7 +1339,7 @@ async def organizations( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1511,7 +1511,7 @@ async def products( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityProductListResponseItemsItem, EntityProductListResponse]: """ - The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The products that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1521,7 +1521,7 @@ async def products( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). @@ -1693,7 +1693,7 @@ async def channels( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[EntityChannelListResponseItemsItem, EntityChannelListResponse]: """ - The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, limit, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total, offset, limit and hasMore mirror the web shape. + The channels that co-occur with this topic in indexed media, with q/field/sort/order filtering. Signed cursor pagination: rows are in items, and next_cursor (null on the last page) feeds cursor. A token binds route, filters, caller and visibility; malformed or old tokens return invalid_cursor. Aggregate and display lists are live: changed ranks or deleted rows may shift later pages. total counts every matching row and limit is the page size applied. Parameters ---------- @@ -1703,7 +1703,7 @@ async def channels( limit : typing.Optional[int] cursor : typing.Optional[str] - Signed continuation from next_cursor. Bound to route, filters, limit, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. + Signed continuation from next_cursor. Bound to route, filters, caller and visibility; invalid or old tokens return invalid_cursor. Person appearance publication pages use a media-id insertion fence; aggregate sorts remain live. q : typing.Optional[str] Substring filter over the row's text columns (e.g. video title, channel name, description). diff --git a/src/arcmira/trackers/alerts/client.py b/src/arcmira/trackers/alerts/client.py index e358a35..a46859b 100644 --- a/src/arcmira/trackers/alerts/client.py +++ b/src/arcmira/trackers/alerts/client.py @@ -24,17 +24,18 @@ def with_raw_response(self) -> RawAlertsClient: return self._raw_client def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AlertListResponse: """ - The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the tracker (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Tracker id, trk_ form. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -55,7 +56,7 @@ def list( id="id", ) """ - _response = self._raw_client.list(id, n=n, request_options=request_options) + _response = self._raw_client.list(id, limit=limit, request_options=request_options) return _response.data @@ -75,17 +76,18 @@ def with_raw_response(self) -> AsyncRawAlertsClient: return self._raw_client async def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AlertListResponse: """ - The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the tracker (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Tracker id, trk_ form. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -114,5 +116,5 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.list(id, n=n, request_options=request_options) + _response = await self._raw_client.list(id, limit=limit, request_options=request_options) return _response.data diff --git a/src/arcmira/trackers/alerts/raw_client.py b/src/arcmira/trackers/alerts/raw_client.py index 6d61f44..5ea9aa4 100644 --- a/src/arcmira/trackers/alerts/raw_client.py +++ b/src/arcmira/trackers/alerts/raw_client.py @@ -26,17 +26,18 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[AlertListResponse]: """ - The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the tracker (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Tracker id, trk_ form. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -50,7 +51,7 @@ def list( f"v1/trackers/{encode_path_param(id)}/alerts", method="GET", params={ - "n": n, + "limit": limit, }, request_options=request_options, ) @@ -145,17 +146,18 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def list( - self, id: str, *, n: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None + self, id: str, *, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[AlertListResponse]: """ - The newest n alert deliveries for the tracker, as a single page. This endpoint does not paginate: has_more is always false and next_cursor is always null. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. + The newest limit alert deliveries for the tracker (default 25, at most 100), as a single page. has_more is true when older alerts exist past limit; this endpoint does not paginate, so next_cursor is always null and a larger limit reads further. entity_id ("ent_{n}") and mention_id ("men_{n}") are public-ID forms that join directly against entity and mention rows; media_id and appearance_id are raw integer ids, matching the numeric ids used elsewhere in the API. Dispute a fired alert via POST /v1/feedback with type monitor_alert. Parameters ---------- id : str Tracker id, trk_ form. - n : typing.Optional[int] + limit : typing.Optional[int] + Alerts to return, newest first, 1 to 100. Default 25. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -169,7 +171,7 @@ async def list( f"v1/trackers/{encode_path_param(id)}/alerts", method="GET", params={ - "n": n, + "limit": limit, }, request_options=request_options, ) diff --git a/src/arcmira/trackers/client.py b/src/arcmira/trackers/client.py index 8201f24..3cd8b5a 100644 --- a/src/arcmira/trackers/client.py +++ b/src/arcmira/trackers/client.py @@ -92,7 +92,7 @@ def create( Entity type of the tracked entity. Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -137,6 +137,7 @@ def create( api_key="YOUR_API_KEY", ) client.trackers.create( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", entity_name="entityName", entity_type="person", ) @@ -174,7 +175,7 @@ def delete( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -193,6 +194,7 @@ def delete( ) client.trackers.delete( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) """ _response = self._raw_client.delete(id, idempotency_key=idempotency_key, request_options=request_options) @@ -224,7 +226,7 @@ def update( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -273,6 +275,7 @@ def update( ) client.trackers.update( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) """ _response = self._raw_client.update( @@ -381,7 +384,7 @@ async def create( Entity type of the tracked entity. Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -431,6 +434,7 @@ async def create( async def main() -> None: await client.trackers.create( + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", entity_name="entityName", entity_type="person", ) @@ -471,7 +475,7 @@ async def delete( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -495,6 +499,7 @@ async def delete( async def main() -> None: await client.trackers.delete( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) @@ -529,7 +534,7 @@ async def update( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -583,6 +588,7 @@ async def update( async def main() -> None: await client.trackers.update( id="id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) diff --git a/src/arcmira/trackers/raw_client.py b/src/arcmira/trackers/raw_client.py index 3afd11f..f5698fe 100644 --- a/src/arcmira/trackers/raw_client.py +++ b/src/arcmira/trackers/raw_client.py @@ -167,7 +167,7 @@ def create( Entity type of the tracked entity. Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -339,7 +339,7 @@ def delete( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -479,7 +479,7 @@ def update( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -777,7 +777,7 @@ async def create( Entity type of the tracked entity. Required on create. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. @@ -949,7 +949,7 @@ async def delete( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1089,7 +1089,7 @@ async def update( Tracker id, trk_ form. idempotency_key : typing.Optional[str] - Use 8 to 128 letters, numbers, underscores or hyphens per intent. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. + One key per intent, 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Keys are scoped to the account, credential and resource family. The same key and normalized method, path and body returns the committed response with Idempotency-Replayed: true. A changed intent within the same family returns 409 idempotency_conflict. Monitor and tracker families have independent namespaces. Secret recovery is limited as described by the operation. display_name : typing.Optional[str] Optional label shown in alerts and the dashboard. diff --git a/src/arcmira/transcripts/client.py b/src/arcmira/transcripts/client.py index 9bec4be..855f49e 100644 --- a/src/arcmira/transcripts/client.py +++ b/src/arcmira/transcripts/client.py @@ -7,8 +7,8 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.pagination import AsyncPager, SyncPager from ..core.request_options import RequestOptions +from ..types.transcript_job import TranscriptJob from ..types.transcript_purchase_quote import TranscriptPurchaseQuote -from ..types.transcript_request import TranscriptRequest from ..types.transcript_request_list_response import TranscriptRequestListResponse from ..types.transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem from ..types.transcript_request_submit_response import TranscriptRequestSubmitResponse @@ -148,7 +148,7 @@ def get( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptResult: """ - Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. Branch on state: 200 ready is owned content; 202 pending carries the job, with Retry-After; 200 preparation_required carries the whole-video quote and the action to take, POST /v1/transcriptions with { video_id }, plus last_attempt when the previous purchase failed. Plans without Premium read captions with an access gate instead. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. Parameters ---------- @@ -156,7 +156,7 @@ def get( YouTube video id, 11 characters. quality : typing.Optional[GetTranscriptsRequestQuality] - captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. @@ -179,7 +179,7 @@ def get( Returns ------- TranscriptResult - The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state preparation_required: Premium is not owned yet; the quote and the POST that prepares it. Examples -------- @@ -208,7 +208,7 @@ def quote( self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> TranscriptPurchaseQuote: """ - Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. A video with no known duration, or one past the 12 hour cap, answers 400 invalid_query with param video_id. Parameters ---------- @@ -279,7 +279,7 @@ def list_requests( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ - Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `eta_seconds` and `next_poll_seconds`. Parameters ---------- @@ -321,32 +321,32 @@ def list_requests( def request( self, *, - idempotency_key: str, - max_rows: int, - max_on_demand_cents: typing.Optional[float] = OMIT, + idempotency_key: typing.Optional[str] = None, video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, + max_rows: typing.Optional[int] = OMIT, + max_on_demand_cents: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptRequestSubmitResponse: """ - Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- - idempotency_key : str - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. - - max_rows : int - Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. - - max_on_demand_cents : typing.Optional[float] - Maximum new monetary on-demand charge in cents. Omit to authorize none. + idempotency_key : typing.Optional[str] + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. video_id : typing.Optional[str] - YouTube video id (11 characters). Either videoId or url is required. + YouTube video id (11 characters). Either video_id or url is required. url : typing.Optional[str] - A YouTube watch/short/live URL. Either videoId or url is required. + A YouTube watch/short/live URL. Either video_id or url is required. + + max_rows : typing.Optional[int] + Maximum whole-video rows authorized. Credit mode charges four credits per row. Omit it to cap the purchase at the current quote. Required with max_on_demand_cents above 0. + + max_on_demand_cents : typing.Optional[int] + Maximum new monetary on-demand charge in whole cents. Defaults to 0, which moves no money. Above 0 it requires Idempotency-Key and max_rows. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -354,7 +354,7 @@ def request( Returns ------- TranscriptRequestSubmitResponse - An existing in-flight or already-satisfied request was returned (existing: true) + job.state is ready, failed or refunded: the transcript is servable, or the purchase ended without one. Examples -------- @@ -364,23 +364,22 @@ def request( api_key="YOUR_API_KEY", ) client.transcripts.request( - idempotency_key="Idempotency-Key", - max_rows=1, + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) """ _response = self._raw_client.request( idempotency_key=idempotency_key, - max_rows=max_rows, - max_on_demand_cents=max_on_demand_cents, video_id=video_id, url=url, + max_rows=max_rows, + max_on_demand_cents=max_on_demand_cents, request_options=request_options, ) return _response.data - def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> TranscriptRequest: + def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> TranscriptJob: """ - Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `eta_seconds` and `next_poll_seconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and next_poll_seconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. Parameters ---------- @@ -392,7 +391,7 @@ def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = Returns ------- - TranscriptRequest + TranscriptJob Success Examples @@ -563,7 +562,7 @@ async def get( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptResult: """ - Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. Branch on state: 200 ready is owned content; 202 pending carries the job, with Retry-After; 200 preparation_required carries the whole-video quote and the action to take, POST /v1/transcriptions with { video_id }, plus last_attempt when the previous purchase failed. Plans without Premium read captions with an access gate instead. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. Parameters ---------- @@ -571,7 +570,7 @@ async def get( YouTube video id, 11 characters. quality : typing.Optional[GetTranscriptsRequestQuality] - captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. @@ -594,7 +593,7 @@ async def get( Returns ------- TranscriptResult - The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state preparation_required: Premium is not owned yet; the quote and the POST that prepares it. Examples -------- @@ -631,7 +630,7 @@ async def quote( self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> TranscriptPurchaseQuote: """ - Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. A video with no known duration, or one past the 12 hour cap, answers 400 invalid_query with param video_id. Parameters ---------- @@ -718,7 +717,7 @@ async def list_requests( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ - Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `eta_seconds` and `next_poll_seconds`. Parameters ---------- @@ -769,32 +768,32 @@ async def main() -> None: async def request( self, *, - idempotency_key: str, - max_rows: int, - max_on_demand_cents: typing.Optional[float] = OMIT, + idempotency_key: typing.Optional[str] = None, video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, + max_rows: typing.Optional[int] = OMIT, + max_on_demand_cents: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptRequestSubmitResponse: """ - Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- - idempotency_key : str - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. - - max_rows : int - Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. - - max_on_demand_cents : typing.Optional[float] - Maximum new monetary on-demand charge in cents. Omit to authorize none. + idempotency_key : typing.Optional[str] + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. video_id : typing.Optional[str] - YouTube video id (11 characters). Either videoId or url is required. + YouTube video id (11 characters). Either video_id or url is required. url : typing.Optional[str] - A YouTube watch/short/live URL. Either videoId or url is required. + A YouTube watch/short/live URL. Either video_id or url is required. + + max_rows : typing.Optional[int] + Maximum whole-video rows authorized. Credit mode charges four credits per row. Omit it to cap the purchase at the current quote. Required with max_on_demand_cents above 0. + + max_on_demand_cents : typing.Optional[int] + Maximum new monetary on-demand charge in whole cents. Defaults to 0, which moves no money. Above 0 it requires Idempotency-Key and max_rows. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -802,7 +801,7 @@ async def request( Returns ------- TranscriptRequestSubmitResponse - An existing in-flight or already-satisfied request was returned (existing: true) + job.state is ready, failed or refunded: the transcript is servable, or the purchase ended without one. Examples -------- @@ -817,8 +816,7 @@ async def request( async def main() -> None: await client.transcripts.request( - idempotency_key="Idempotency-Key", - max_rows=1, + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", ) @@ -826,17 +824,17 @@ async def main() -> None: """ _response = await self._raw_client.request( idempotency_key=idempotency_key, - max_rows=max_rows, - max_on_demand_cents=max_on_demand_cents, video_id=video_id, url=url, + max_rows=max_rows, + max_on_demand_cents=max_on_demand_cents, request_options=request_options, ) return _response.data - async def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> TranscriptRequest: + async def status(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> TranscriptJob: """ - Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `eta_seconds` and `next_poll_seconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and next_poll_seconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. Parameters ---------- @@ -848,7 +846,7 @@ async def status(self, id: str, *, request_options: typing.Optional[RequestOptio Returns ------- - TranscriptRequest + TranscriptJob Success Examples diff --git a/src/arcmira/transcripts/edits/client.py b/src/arcmira/transcripts/edits/client.py index 9719d0d..cdac5a6 100644 --- a/src/arcmira/transcripts/edits/client.py +++ b/src/arcmira/transcripts/edits/client.py @@ -54,7 +54,7 @@ def submit( corrected_text : str idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. revision : typing.Optional[str] The revision from the Premium transcript read. @@ -76,6 +76,7 @@ def submit( ) client.transcripts.edits.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", segment_index=1, original_text="originalText", corrected_text="correctedText", @@ -170,7 +171,7 @@ async def submit( corrected_text : str idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. revision : typing.Optional[str] The revision from the Premium transcript read. @@ -197,6 +198,7 @@ async def submit( async def main() -> None: await client.transcripts.edits.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", segment_index=1, original_text="originalText", corrected_text="correctedText", diff --git a/src/arcmira/transcripts/edits/raw_client.py b/src/arcmira/transcripts/edits/raw_client.py index 2a02bb1..c1b1411 100644 --- a/src/arcmira/transcripts/edits/raw_client.py +++ b/src/arcmira/transcripts/edits/raw_client.py @@ -57,7 +57,7 @@ def submit( corrected_text : str idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. revision : typing.Optional[str] The revision from the Premium transcript read. @@ -324,7 +324,7 @@ async def submit( corrected_text : str idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. revision : typing.Optional[str] The revision from the Premium transcript read. diff --git a/src/arcmira/transcripts/merges/client.py b/src/arcmira/transcripts/merges/client.py index 394a650..ddc3e08 100644 --- a/src/arcmira/transcripts/merges/client.py +++ b/src/arcmira/transcripts/merges/client.py @@ -83,7 +83,7 @@ def submit( The canonical entity these mentions actually refer to. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. replace_with : typing.Optional[str] Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). @@ -107,6 +107,7 @@ def submit( ) client.transcripts.merges.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", source_name="sourceName", target_entity_id=1, ) @@ -238,7 +239,7 @@ async def submit( The canonical entity these mentions actually refer to. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. replace_with : typing.Optional[str] Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). @@ -267,6 +268,7 @@ async def submit( async def main() -> None: await client.transcripts.merges.submit( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", source_name="sourceName", target_entity_id=1, ) diff --git a/src/arcmira/transcripts/merges/raw_client.py b/src/arcmira/transcripts/merges/raw_client.py index e8f1fe3..b6d109e 100644 --- a/src/arcmira/transcripts/merges/raw_client.py +++ b/src/arcmira/transcripts/merges/raw_client.py @@ -164,7 +164,7 @@ def submit( The canonical entity these mentions actually refer to. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. replace_with : typing.Optional[str] Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). @@ -539,7 +539,7 @@ async def submit( The canonical entity these mentions actually refer to. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. replace_with : typing.Optional[str] Optional respelling applied to the transcript text (e.g. "Imad" → "Emad"). diff --git a/src/arcmira/transcripts/raw_client.py b/src/arcmira/transcripts/raw_client.py index 84f2c05..190e0e3 100644 --- a/src/arcmira/transcripts/raw_client.py +++ b/src/arcmira/transcripts/raw_client.py @@ -20,10 +20,9 @@ from ..errors.service_unavailable_error import ServiceUnavailableError from ..errors.too_many_requests_error import TooManyRequestsError from ..errors.unauthorized_error import UnauthorizedError -from ..errors.unprocessable_entity_error import UnprocessableEntityError from ..types.error import Error +from ..types.transcript_job import TranscriptJob from ..types.transcript_purchase_quote import TranscriptPurchaseQuote -from ..types.transcript_request import TranscriptRequest from ..types.transcript_request_list_response import TranscriptRequestListResponse from ..types.transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem from ..types.transcript_request_submit_response import TranscriptRequestSubmitResponse @@ -242,7 +241,7 @@ def get( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptResult]: """ - Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. Branch on state: 200 ready is owned content; 202 pending carries the job, with Retry-After; 200 preparation_required carries the whole-video quote and the action to take, POST /v1/transcriptions with { video_id }, plus last_attempt when the previous purchase failed. Plans without Premium read captions with an access gate instead. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. Parameters ---------- @@ -250,7 +249,7 @@ def get( YouTube video id, 11 characters. quality : typing.Optional[GetTranscriptsRequestQuality] - captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. @@ -273,7 +272,7 @@ def get( Returns ------- HttpResponse[TranscriptResult] - The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state preparation_required: Premium is not owned yet; the quote and the POST that prepares it. """ _response = self._client_wrapper.httpx_client.request( f"v1/transcripts/{encode_path_param(video_id)}", @@ -399,7 +398,7 @@ def quote( self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[TranscriptPurchaseQuote]: """ - Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. A video with no known duration, or one past the 12 hour cap, answers 400 invalid_query with param video_id. Parameters ---------- @@ -633,7 +632,7 @@ def list_requests( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ - Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `eta_seconds` and `next_poll_seconds`. Parameters ---------- @@ -761,32 +760,32 @@ def list_requests( def request( self, *, - idempotency_key: str, - max_rows: int, - max_on_demand_cents: typing.Optional[float] = OMIT, + idempotency_key: typing.Optional[str] = None, video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, + max_rows: typing.Optional[int] = OMIT, + max_on_demand_cents: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptRequestSubmitResponse]: """ - Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- - idempotency_key : str - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. - - max_rows : int - Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. - - max_on_demand_cents : typing.Optional[float] - Maximum new monetary on-demand charge in cents. Omit to authorize none. + idempotency_key : typing.Optional[str] + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. video_id : typing.Optional[str] - YouTube video id (11 characters). Either videoId or url is required. + YouTube video id (11 characters). Either video_id or url is required. url : typing.Optional[str] - A YouTube watch/short/live URL. Either videoId or url is required. + A YouTube watch/short/live URL. Either video_id or url is required. + + max_rows : typing.Optional[int] + Maximum whole-video rows authorized. Credit mode charges four credits per row. Omit it to cap the purchase at the current quote. Required with max_on_demand_cents above 0. + + max_on_demand_cents : typing.Optional[int] + Maximum new monetary on-demand charge in whole cents. Defaults to 0, which moves no money. Above 0 it requires Idempotency-Key and max_rows. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -794,16 +793,16 @@ def request( Returns ------- HttpResponse[TranscriptRequestSubmitResponse] - An existing in-flight or already-satisfied request was returned (existing: true) + job.state is ready, failed or refunded: the transcript is servable, or the purchase ended without one. """ _response = self._client_wrapper.httpx_client.request( "v1/transcriptions", method="POST", json={ - "max_on_demand_cents": max_on_demand_cents, - "max_rows": max_rows, - "videoId": video_id, + "video_id": video_id, "url": url, + "max_rows": max_rows, + "max_on_demand_cents": max_on_demand_cents, }, headers={ "content-type": "application/json", @@ -888,17 +887,6 @@ def request( ), ), ) - if _response.status_code == 422: - raise UnprocessableEntityError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), @@ -932,9 +920,9 @@ def request( def status( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[TranscriptRequest]: + ) -> HttpResponse[TranscriptJob]: """ - Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `eta_seconds` and `next_poll_seconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and next_poll_seconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. Parameters ---------- @@ -946,7 +934,7 @@ def status( Returns ------- - HttpResponse[TranscriptRequest] + HttpResponse[TranscriptJob] Success """ _response = self._client_wrapper.httpx_client.request( @@ -957,9 +945,9 @@ def status( try: if 200 <= _response.status_code < 300: _data = typing.cast( - TranscriptRequest, + TranscriptJob, parse_obj_as( - type_=TranscriptRequest, # type: ignore + type_=TranscriptJob, # type: ignore object_=_response.json(), ), ) @@ -1244,7 +1232,7 @@ async def get( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptResult]: """ - Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. It returns owned ready content, 202 pending with a status URL, or 403 purchase_required with quote and prepare URLs. Purchase the full video explicitly through POST /v1/transcriptions. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. + Caption retrieval costs one row per started 15 minutes. Premium retrieval is free and never buys, generates, or returns fallback captions. Branch on state: 200 ready is owned content; 202 pending carries the job, with Retry-After; 200 preparation_required carries the whole-video quote and the action to take, POST /v1/transcriptions with { video_id }, plus last_attempt when the previous purchase failed. Plans without Premium read captions with an access gate instead. start/end only trim the returned content; language selects caption tracks, timestamps=false returns paragraphs. Premium responses retain revision and line indexes for corrections. Parameters ---------- @@ -1252,7 +1240,7 @@ async def get( YouTube video id, 11 characters. quality : typing.Optional[GetTranscriptsRequestQuality] - captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 at zero rows, an active purchase returns 202 with status_url and next_poll_seconds, and an unowned transcript returns 403 purchase_required with quote_url and prepare_url. It never purchases or substitutes captions. Quote and explicitly purchase the whole video before reading Premium. Default captions unless changed in account settings. + captions reads creator or automatic captions at 1 row per started 15 minutes. premium is read-only: an owned transcript returns 200 state ready at zero rows, an active purchase returns 202 state pending with its job, and an unowned transcript on a plan with Premium returns 200 state preparation_required with the quote and the POST /v1/transcriptions action. It never purchases or substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. @@ -1275,7 +1263,7 @@ async def get( Returns ------- AsyncHttpResponse[TranscriptResult] - The transcript: video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note. + state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state preparation_required: Premium is not owned yet; the quote and the POST that prepares it. """ _response = await self._client_wrapper.httpx_client.request( f"v1/transcripts/{encode_path_param(video_id)}", @@ -1401,7 +1389,7 @@ async def quote( self, video_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[TranscriptPurchaseQuote]: """ - Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. + Optional free quote. It does not reserve funds or start generation. max_rows authorizes rows, while max_on_demand_cents separately authorizes new money and defaults to zero on purchase. The accepted purchase stores its pricing mode. A video with no known duration, or one past the 12 hour cap, answers 400 invalid_query with param video_id. Parameters ---------- @@ -1635,7 +1623,7 @@ async def list_requests( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[TranscriptRequestListResponseRequestsItem, TranscriptRequestListResponse]: """ - Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `etaSeconds` + `nextPollSeconds`. + Your transcription requests in descending creation time and id order. limit defaults to 20 and accepts 1–100. Follow next_cursor with the same video_id, limit and credential; has_more is false and next_cursor is null on the last page. A traversal excludes requests inserted after its first page. Each entry has the same shape as the status poll plus a `title` field (the video title, null when unknown). The scheduled reconciler advances requests; reading this list never dispatches work or changes billing. In-flight entries carry `eta_seconds` and `next_poll_seconds`. Parameters ---------- @@ -1766,32 +1754,32 @@ async def _get_next(): async def request( self, *, - idempotency_key: str, - max_rows: int, - max_on_demand_cents: typing.Optional[float] = OMIT, + idempotency_key: typing.Optional[str] = None, video_id: typing.Optional[str] = OMIT, url: typing.Optional[str] = OMIT, + max_rows: typing.Optional[int] = OMIT, + max_on_demand_cents: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptRequestSubmitResponse]: """ - Explicit whole-video purchase. Requires Idempotency-Key and max_rows; max_on_demand_cents defaults to zero. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. Poll the returned request with Retry-After. Pending work returns 202 and an existing artifact returns 201. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- - idempotency_key : str - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. - - max_rows : int - Maximum whole-video rows authorized. Credit mode charges four credits per row. Required even when submitting without a quote. - - max_on_demand_cents : typing.Optional[float] - Maximum new monetary on-demand charge in cents. Omit to authorize none. + idempotency_key : typing.Optional[str] + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. video_id : typing.Optional[str] - YouTube video id (11 characters). Either videoId or url is required. + YouTube video id (11 characters). Either video_id or url is required. url : typing.Optional[str] - A YouTube watch/short/live URL. Either videoId or url is required. + A YouTube watch/short/live URL. Either video_id or url is required. + + max_rows : typing.Optional[int] + Maximum whole-video rows authorized. Credit mode charges four credits per row. Omit it to cap the purchase at the current quote. Required with max_on_demand_cents above 0. + + max_on_demand_cents : typing.Optional[int] + Maximum new monetary on-demand charge in whole cents. Defaults to 0, which moves no money. Above 0 it requires Idempotency-Key and max_rows. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1799,16 +1787,16 @@ async def request( Returns ------- AsyncHttpResponse[TranscriptRequestSubmitResponse] - An existing in-flight or already-satisfied request was returned (existing: true) + job.state is ready, failed or refunded: the transcript is servable, or the purchase ended without one. """ _response = await self._client_wrapper.httpx_client.request( "v1/transcriptions", method="POST", json={ - "max_on_demand_cents": max_on_demand_cents, - "max_rows": max_rows, - "videoId": video_id, + "video_id": video_id, "url": url, + "max_rows": max_rows, + "max_on_demand_cents": max_on_demand_cents, }, headers={ "content-type": "application/json", @@ -1893,17 +1881,6 @@ async def request( ), ), ) - if _response.status_code == 422: - raise UnprocessableEntityError( - headers=dict(_response.headers), - body=typing.cast( - Error, - parse_obj_as( - type_=Error, # type: ignore - object_=_response.json(), - ), - ), - ) if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), @@ -1937,9 +1914,9 @@ async def request( async def status( self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[TranscriptRequest]: + ) -> AsyncHttpResponse[TranscriptJob]: """ - Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `etaSeconds` + `nextPollSeconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and nextPollSeconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. + Agent-friendly polling contract: while the request is in flight the response carries a Retry-After header (seconds) and body fields `eta_seconds` and `next_poll_seconds`. Sleep on Retry-After and re-poll. `status` walks queued → downloading → transcribing → analyzing → complete (user-facing `stage` folds downloading into transcribing). refund_pending retains Retry-After and next_poll_seconds until reversal completes; it has no completion ETA. Terminal statuses (`complete`, `failed`, `refunded`) drop Retry-After. On `complete`, fetch the transcript via GET /v1/transcripts/{video_id}; the successful purchase owns the permanent unlock. `refunded` means the pipeline failed and the rows were returned. A caller with no account holds no jobs: it is refused with 401 job_requires_account, whose unlock points at sign-up. Parameters ---------- @@ -1951,7 +1928,7 @@ async def status( Returns ------- - AsyncHttpResponse[TranscriptRequest] + AsyncHttpResponse[TranscriptJob] Success """ _response = await self._client_wrapper.httpx_client.request( @@ -1962,9 +1939,9 @@ async def status( try: if 200 <= _response.status_code < 300: _data = typing.cast( - TranscriptRequest, + TranscriptJob, parse_obj_as( - type_=TranscriptRequest, # type: ignore + type_=TranscriptJob, # type: ignore object_=_response.json(), ), ) diff --git a/src/arcmira/transcripts/speakers/client.py b/src/arcmira/transcripts/speakers/client.py index 06c6939..bcc2e9f 100644 --- a/src/arcmira/transcripts/speakers/client.py +++ b/src/arcmira/transcripts/speakers/client.py @@ -50,7 +50,7 @@ def identify( A speakers[].id from the Premium transcript read that revision names. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. entity_id : typing.Optional[int] Existing person entity id. Either entityId or name is required. @@ -78,6 +78,7 @@ def identify( ) client.transcripts.speakers.identify( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", speaker_id=1, ) """ @@ -168,7 +169,7 @@ async def identify( A speakers[].id from the Premium transcript read that revision names. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. entity_id : typing.Optional[int] Existing person entity id. Either entityId or name is required. @@ -201,6 +202,7 @@ async def identify( async def main() -> None: await client.transcripts.speakers.identify( video_id="video_id", + idempotency_key="8b2f6c3e-4d1a-4e7b-9c05-2f6a1b7d3e90", speaker_id=1, ) diff --git a/src/arcmira/transcripts/speakers/raw_client.py b/src/arcmira/transcripts/speakers/raw_client.py index 2a9b495..fac6f4f 100644 --- a/src/arcmira/transcripts/speakers/raw_client.py +++ b/src/arcmira/transcripts/speakers/raw_client.py @@ -53,7 +53,7 @@ def identify( A speakers[].id from the Premium transcript read that revision names. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. entity_id : typing.Optional[int] Existing person entity id. Either entityId or name is required. @@ -324,7 +324,7 @@ async def identify( A speakers[].id from the Premium transcript read that revision names. idempotency_key : typing.Optional[str] - Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. + 1 to 255 printable ASCII characters (0x21 to 0x7E); anything else is 400 invalid_idempotency_key. Persist a unique key and the exact request before sending a logical mutation. A retry returns its stored response with Idempotency-Replayed: true. A changed intent under a finalized key returns 409 idempotency_conflict. Keys belong to the authenticated owner, credential and mutation domain. Current authorization still applies. Receipts have no general 24-hour expiry; signing-secret recovery alone expires after 24 hours or when the secret is displaced. entity_id : typing.Optional[int] Existing person entity id. Either entityId or name is required. diff --git a/src/arcmira/types/__init__.py b/src/arcmira/types/__init__.py index 0ca8462..30939a7 100644 --- a/src/arcmira/types/__init__.py +++ b/src/arcmira/types/__init__.py @@ -62,7 +62,6 @@ from .channel_videos_response_episodes_item import ChannelVideosResponseEpisodesItem from .correction_accepted_response import CorrectionAcceptedResponse from .correction_accepted_response_kind import CorrectionAcceptedResponseKind - from .correction_seq_mismatch_response import CorrectionSeqMismatchResponse from .delivery_issue_change import DeliveryIssueChange from .delivery_issue_change_channel import DeliveryIssueChangeChannel from .entity import Entity @@ -352,23 +351,34 @@ from .transcript_edit_submitted_response import TranscriptEditSubmittedResponse from .transcript_edit_submitted_response_edit import TranscriptEditSubmittedResponseEdit from .transcript_edit_submitted_response_edit_status import TranscriptEditSubmittedResponseEditStatus + from .transcript_job import TranscriptJob + from .transcript_job_charge import TranscriptJobCharge + from .transcript_job_charge_from import TranscriptJobChargeFrom + from .transcript_job_charge_unit import TranscriptJobChargeUnit + from .transcript_job_stage import TranscriptJobStage + from .transcript_job_state import TranscriptJobState + from .transcript_job_status import TranscriptJobStatus from .transcript_pending import TranscriptPending - from .transcript_pending_premium_job import TranscriptPendingPremiumJob from .transcript_pending_quality import TranscriptPendingQuality + from .transcript_preparation_required import TranscriptPreparationRequired + from .transcript_preparation_required_action import TranscriptPreparationRequiredAction + from .transcript_preparation_required_action_body import TranscriptPreparationRequiredActionBody + from .transcript_preparation_required_action_method import TranscriptPreparationRequiredActionMethod + from .transcript_preparation_required_last_attempt import TranscriptPreparationRequiredLastAttempt + from .transcript_preparation_required_quality import TranscriptPreparationRequiredQuality + from .transcript_preparation_required_quote import TranscriptPreparationRequiredQuote + from .transcript_preparation_required_quote_charge import TranscriptPreparationRequiredQuoteCharge + from .transcript_preparation_required_quote_charge_from import TranscriptPreparationRequiredQuoteChargeFrom + from .transcript_preparation_required_quote_charge_unit import TranscriptPreparationRequiredQuoteChargeUnit from .transcript_purchase_quote import TranscriptPurchaseQuote from .transcript_purchase_quote_billing_scope import TranscriptPurchaseQuoteBillingScope from .transcript_purchase_quote_charge import TranscriptPurchaseQuoteCharge + from .transcript_purchase_quote_charge_from import TranscriptPurchaseQuoteChargeFrom from .transcript_purchase_quote_charge_unit import TranscriptPurchaseQuoteChargeUnit + from .transcript_purchase_quote_upgrade import TranscriptPurchaseQuoteUpgrade from .transcript_quote import TranscriptQuote - from .transcript_request import TranscriptRequest - from .transcript_request_charge import TranscriptRequestCharge - from .transcript_request_charge_unit import TranscriptRequestChargeUnit from .transcript_request_list_response import TranscriptRequestListResponse from .transcript_request_list_response_requests_item import TranscriptRequestListResponseRequestsItem - from .transcript_request_quote import TranscriptRequestQuote - from .transcript_request_stage import TranscriptRequestStage - from .transcript_request_state import TranscriptRequestState - from .transcript_request_status import TranscriptRequestStatus from .transcript_request_submit_response import TranscriptRequestSubmitResponse from .transcript_response import TranscriptResponse from .transcript_response_access import TranscriptResponseAccess @@ -379,12 +389,16 @@ from .transcript_response_access_unlock_action import TranscriptResponseAccessUnlockAction from .transcript_response_lines_item import TranscriptResponseLinesItem from .transcript_response_paragraphs_item import TranscriptResponseParagraphsItem - from .transcript_response_premium_job import TranscriptResponsePremiumJob from .transcript_response_quality import TranscriptResponseQuality from .transcript_response_range import TranscriptResponseRange from .transcript_response_source import TranscriptResponseSource from .transcript_response_speakers_item import TranscriptResponseSpeakersItem - from .transcript_result import TranscriptResult, TranscriptResult_Pending, TranscriptResult_Ready + from .transcript_result import ( + TranscriptResult, + TranscriptResult_Pending, + TranscriptResult_PreparationRequired, + TranscriptResult_Ready, + ) from .transcript_search_chunk import TranscriptSearchChunk from .transcript_search_response import TranscriptSearchResponse from .transcript_search_response_access import TranscriptSearchResponseAccess @@ -470,7 +484,6 @@ "ChannelVideosResponseEpisodesItem": ".channel_videos_response_episodes_item", "CorrectionAcceptedResponse": ".correction_accepted_response", "CorrectionAcceptedResponseKind": ".correction_accepted_response_kind", - "CorrectionSeqMismatchResponse": ".correction_seq_mismatch_response", "DeliveryIssueChange": ".delivery_issue_change", "DeliveryIssueChangeChannel": ".delivery_issue_change_channel", "Entity": ".entity", @@ -748,23 +761,34 @@ "TranscriptEditSubmittedResponse": ".transcript_edit_submitted_response", "TranscriptEditSubmittedResponseEdit": ".transcript_edit_submitted_response_edit", "TranscriptEditSubmittedResponseEditStatus": ".transcript_edit_submitted_response_edit_status", + "TranscriptJob": ".transcript_job", + "TranscriptJobCharge": ".transcript_job_charge", + "TranscriptJobChargeFrom": ".transcript_job_charge_from", + "TranscriptJobChargeUnit": ".transcript_job_charge_unit", + "TranscriptJobStage": ".transcript_job_stage", + "TranscriptJobState": ".transcript_job_state", + "TranscriptJobStatus": ".transcript_job_status", "TranscriptPending": ".transcript_pending", - "TranscriptPendingPremiumJob": ".transcript_pending_premium_job", "TranscriptPendingQuality": ".transcript_pending_quality", + "TranscriptPreparationRequired": ".transcript_preparation_required", + "TranscriptPreparationRequiredAction": ".transcript_preparation_required_action", + "TranscriptPreparationRequiredActionBody": ".transcript_preparation_required_action_body", + "TranscriptPreparationRequiredActionMethod": ".transcript_preparation_required_action_method", + "TranscriptPreparationRequiredLastAttempt": ".transcript_preparation_required_last_attempt", + "TranscriptPreparationRequiredQuality": ".transcript_preparation_required_quality", + "TranscriptPreparationRequiredQuote": ".transcript_preparation_required_quote", + "TranscriptPreparationRequiredQuoteCharge": ".transcript_preparation_required_quote_charge", + "TranscriptPreparationRequiredQuoteChargeFrom": ".transcript_preparation_required_quote_charge_from", + "TranscriptPreparationRequiredQuoteChargeUnit": ".transcript_preparation_required_quote_charge_unit", "TranscriptPurchaseQuote": ".transcript_purchase_quote", "TranscriptPurchaseQuoteBillingScope": ".transcript_purchase_quote_billing_scope", "TranscriptPurchaseQuoteCharge": ".transcript_purchase_quote_charge", + "TranscriptPurchaseQuoteChargeFrom": ".transcript_purchase_quote_charge_from", "TranscriptPurchaseQuoteChargeUnit": ".transcript_purchase_quote_charge_unit", + "TranscriptPurchaseQuoteUpgrade": ".transcript_purchase_quote_upgrade", "TranscriptQuote": ".transcript_quote", - "TranscriptRequest": ".transcript_request", - "TranscriptRequestCharge": ".transcript_request_charge", - "TranscriptRequestChargeUnit": ".transcript_request_charge_unit", "TranscriptRequestListResponse": ".transcript_request_list_response", "TranscriptRequestListResponseRequestsItem": ".transcript_request_list_response_requests_item", - "TranscriptRequestQuote": ".transcript_request_quote", - "TranscriptRequestStage": ".transcript_request_stage", - "TranscriptRequestState": ".transcript_request_state", - "TranscriptRequestStatus": ".transcript_request_status", "TranscriptRequestSubmitResponse": ".transcript_request_submit_response", "TranscriptResponse": ".transcript_response", "TranscriptResponseAccess": ".transcript_response_access", @@ -775,13 +799,13 @@ "TranscriptResponseAccessUnlockAction": ".transcript_response_access_unlock_action", "TranscriptResponseLinesItem": ".transcript_response_lines_item", "TranscriptResponseParagraphsItem": ".transcript_response_paragraphs_item", - "TranscriptResponsePremiumJob": ".transcript_response_premium_job", "TranscriptResponseQuality": ".transcript_response_quality", "TranscriptResponseRange": ".transcript_response_range", "TranscriptResponseSource": ".transcript_response_source", "TranscriptResponseSpeakersItem": ".transcript_response_speakers_item", "TranscriptResult": ".transcript_result", "TranscriptResult_Pending": ".transcript_result", + "TranscriptResult_PreparationRequired": ".transcript_result", "TranscriptResult_Ready": ".transcript_result", "TranscriptSearchChunk": ".transcript_search_chunk", "TranscriptSearchResponse": ".transcript_search_response", @@ -892,7 +916,6 @@ def __dir__(): "ChannelVideosResponseEpisodesItem", "CorrectionAcceptedResponse", "CorrectionAcceptedResponseKind", - "CorrectionSeqMismatchResponse", "DeliveryIssueChange", "DeliveryIssueChangeChannel", "Entity", @@ -1170,23 +1193,34 @@ def __dir__(): "TranscriptEditSubmittedResponse", "TranscriptEditSubmittedResponseEdit", "TranscriptEditSubmittedResponseEditStatus", + "TranscriptJob", + "TranscriptJobCharge", + "TranscriptJobChargeFrom", + "TranscriptJobChargeUnit", + "TranscriptJobStage", + "TranscriptJobState", + "TranscriptJobStatus", "TranscriptPending", - "TranscriptPendingPremiumJob", "TranscriptPendingQuality", + "TranscriptPreparationRequired", + "TranscriptPreparationRequiredAction", + "TranscriptPreparationRequiredActionBody", + "TranscriptPreparationRequiredActionMethod", + "TranscriptPreparationRequiredLastAttempt", + "TranscriptPreparationRequiredQuality", + "TranscriptPreparationRequiredQuote", + "TranscriptPreparationRequiredQuoteCharge", + "TranscriptPreparationRequiredQuoteChargeFrom", + "TranscriptPreparationRequiredQuoteChargeUnit", "TranscriptPurchaseQuote", "TranscriptPurchaseQuoteBillingScope", "TranscriptPurchaseQuoteCharge", + "TranscriptPurchaseQuoteChargeFrom", "TranscriptPurchaseQuoteChargeUnit", + "TranscriptPurchaseQuoteUpgrade", "TranscriptQuote", - "TranscriptRequest", - "TranscriptRequestCharge", - "TranscriptRequestChargeUnit", "TranscriptRequestListResponse", "TranscriptRequestListResponseRequestsItem", - "TranscriptRequestQuote", - "TranscriptRequestStage", - "TranscriptRequestState", - "TranscriptRequestStatus", "TranscriptRequestSubmitResponse", "TranscriptResponse", "TranscriptResponseAccess", @@ -1197,13 +1231,13 @@ def __dir__(): "TranscriptResponseAccessUnlockAction", "TranscriptResponseLinesItem", "TranscriptResponseParagraphsItem", - "TranscriptResponsePremiumJob", "TranscriptResponseQuality", "TranscriptResponseRange", "TranscriptResponseSource", "TranscriptResponseSpeakersItem", "TranscriptResult", "TranscriptResult_Pending", + "TranscriptResult_PreparationRequired", "TranscriptResult_Ready", "TranscriptSearchChunk", "TranscriptSearchResponse", diff --git a/src/arcmira/types/alert_list_response.py b/src/arcmira/types/alert_list_response.py index 46bd150..f111fcb 100644 --- a/src/arcmira/types/alert_list_response.py +++ b/src/arcmira/types/alert_list_response.py @@ -15,7 +15,7 @@ class AlertListResponse(UniversalBaseModel): has_more: bool = pydantic.Field() """ - CURRENTLY always false: this endpoint returns the newest n alerts as a single page and does not paginate. + True when older alerts exist past limit. The endpoint does not paginate: raise limit, up to 100, to read them. """ next_cursor: typing.Optional[typing.Any] = pydantic.Field(default=None) diff --git a/src/arcmira/types/channel_guest_list_response.py b/src/arcmira/types/channel_guest_list_response.py index b1c8bb6..145ce7c 100644 --- a/src/arcmira/types/channel_guest_list_response.py +++ b/src/arcmira/types/channel_guest_list_response.py @@ -22,25 +22,11 @@ class ChannelGuestListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/channel_sponsors_response_access.py b/src/arcmira/types/channel_sponsors_response_access.py index 200737f..6adac71 100644 --- a/src/arcmira/types/channel_sponsors_response_access.py +++ b/src/arcmira/types/channel_sponsors_response_access.py @@ -55,6 +55,16 @@ class ChannelSponsorsResponseAccess(UniversalBaseModel): Present on rate gates. Mirrors the Retry-After header. """ + current_revision: typing.Optional[str] = pydantic.Field(default=None) + """ + On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction. + """ + + expected_seq: typing.Optional[int] = pydantic.Field(default=None) + """ + On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key. + """ + doc_url: str request_id: str diff --git a/src/arcmira/types/correction_seq_mismatch_response.py b/src/arcmira/types/correction_seq_mismatch_response.py deleted file mode 100644 index 91256e4..0000000 --- a/src/arcmira/types/correction_seq_mismatch_response.py +++ /dev/null @@ -1,36 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata - - -class CorrectionSeqMismatchResponse(UniversalBaseModel): - error: str = pydantic.Field() - """ - Always "Out-of-order correction.". - """ - - expected_seq: typing_extensions.Annotated[ - int, - FieldMetadata(alias="expectedSeq"), - pydantic.Field( - alias="expectedSeq", - description="The seq the server expects next for this video. Rebase local counters onto it and resend.", - ), - ] - """ - The seq the server expects next for this video. Rebase local counters onto it and resend. - """ - - 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/arcmira/types/entity_channel_list_response.py b/src/arcmira/types/entity_channel_list_response.py index 8fe3f63..39b6ada 100644 --- a/src/arcmira/types/entity_channel_list_response.py +++ b/src/arcmira/types/entity_channel_list_response.py @@ -22,25 +22,11 @@ class EntityChannelListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/entity_momentum_response_access.py b/src/arcmira/types/entity_momentum_response_access.py index b5c3d61..71aa044 100644 --- a/src/arcmira/types/entity_momentum_response_access.py +++ b/src/arcmira/types/entity_momentum_response_access.py @@ -55,6 +55,16 @@ class EntityMomentumResponseAccess(UniversalBaseModel): Present on rate gates. Mirrors the Retry-After header. """ + current_revision: typing.Optional[str] = pydantic.Field(default=None) + """ + On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction. + """ + + expected_seq: typing.Optional[int] = pydantic.Field(default=None) + """ + On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key. + """ + doc_url: str request_id: str diff --git a/src/arcmira/types/entity_organization_list_response.py b/src/arcmira/types/entity_organization_list_response.py index 47c07e3..5a2651e 100644 --- a/src/arcmira/types/entity_organization_list_response.py +++ b/src/arcmira/types/entity_organization_list_response.py @@ -22,25 +22,11 @@ class EntityOrganizationListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/entity_people_list_response.py b/src/arcmira/types/entity_people_list_response.py index ce5f62f..3ed45e0 100644 --- a/src/arcmira/types/entity_people_list_response.py +++ b/src/arcmira/types/entity_people_list_response.py @@ -23,25 +23,11 @@ class EntityPeopleListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/entity_product_list_response.py b/src/arcmira/types/entity_product_list_response.py index 065d640..d8b4676 100644 --- a/src/arcmira/types/entity_product_list_response.py +++ b/src/arcmira/types/entity_product_list_response.py @@ -22,25 +22,11 @@ class EntityProductListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/entity_topic_list_response.py b/src/arcmira/types/entity_topic_list_response.py index 361cd6b..2bd42d0 100644 --- a/src/arcmira/types/entity_topic_list_response.py +++ b/src/arcmira/types/entity_topic_list_response.py @@ -22,25 +22,11 @@ class EntityTopicListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/error_error.py b/src/arcmira/types/error_error.py index f044613..17b6509 100644 --- a/src/arcmira/types/error_error.py +++ b/src/arcmira/types/error_error.py @@ -51,6 +51,16 @@ class ErrorError(UniversalBaseModel): Present on rate gates. Mirrors the Retry-After header. """ + current_revision: typing.Optional[str] = pydantic.Field(default=None) + """ + On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction. + """ + + expected_seq: typing.Optional[int] = pydantic.Field(default=None) + """ + On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key. + """ + doc_url: str request_id: str diff --git a/src/arcmira/types/person_appearance_list_response.py b/src/arcmira/types/person_appearance_list_response.py index 1b1dc35..ca77cb7 100644 --- a/src/arcmira/types/person_appearance_list_response.py +++ b/src/arcmira/types/person_appearance_list_response.py @@ -21,25 +21,11 @@ class PersonAppearanceListResponse(UniversalBaseModel): Rows matching the filter across all pages. """ - offset: int = pydantic.Field() - """ - Row offset of this page, as the cursor encoded it. 0 on the first page. - """ - limit: int = pydantic.Field() """ Page size applied, after the plan clamp. """ - has_more: typing_extensions.Annotated[ - bool, - FieldMetadata(alias="hasMore"), - pydantic.Field(alias="hasMore", description="Same value as has_more, kept for readers of the web shape."), - ] - """ - Same value as has_more, kept for readers of the web shape. - """ - has_more: bool = pydantic.Field() """ True when more rows exist past this page. diff --git a/src/arcmira/types/transcript_job.py b/src/arcmira/types/transcript_job.py new file mode 100644 index 0000000..e5ef0ea --- /dev/null +++ b/src/arcmira/types/transcript_job.py @@ -0,0 +1,90 @@ +# 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 .transcript_job_charge import TranscriptJobCharge +from .transcript_job_stage import TranscriptJobStage +from .transcript_job_state import TranscriptJobState +from .transcript_job_status import TranscriptJobStatus + + +class TranscriptJob(UniversalBaseModel): + """ + Your open Premium purchase for this video, when captions were served while it prepares. + """ + + id: str = pydantic.Field() + """ + Transcription request id (UUID). + """ + + video_id: str = pydantic.Field() + """ + YouTube video id (11 characters). + """ + + state: TranscriptJobState = pydantic.Field() + """ + Coarse outcome: pending until the Premium transcript is servable (ready), the purchase failed, or it was refunded. + """ + + status: TranscriptJobStatus = pydantic.Field() + """ + Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked). + """ + + stage: typing.Optional[TranscriptJobStage] = pydantic.Field(default=None) + """ + User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses. + """ + + charge: typing.Optional[TranscriptJobCharge] = pydantic.Field(default=None) + """ + What the purchase charged. Present on durable purchases; absent only on legacy requests. + """ + + eta_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Estimated seconds until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight. + """ + + next_poll_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + Seconds to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight. + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Failure reason. Only present when state is failed or refunded. + """ + + refunded: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when the charge was returned. Only present when state is failed or refunded. + """ + + created_at: str = pydantic.Field() + """ + When the request was submitted. + """ + + completed_at: typing.Optional[str] = pydantic.Field(default=None) + """ + When the request reached a terminal status. Absent while in flight. + """ + + status_url: str = pydantic.Field() + """ + Absolute URL of GET /v1/transcriptions/{id} for this job. + """ + + 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/arcmira/types/transcript_job_charge.py b/src/arcmira/types/transcript_job_charge.py new file mode 100644 index 0000000..e4b1c24 --- /dev/null +++ b/src/arcmira/types/transcript_job_charge.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcript_job_charge_from import TranscriptJobChargeFrom +from .transcript_job_charge_unit import TranscriptJobChargeUnit + + +class TranscriptJobCharge(UniversalBaseModel): + """ + What the purchase charged. Present on durable purchases; absent only on legacy requests. + """ + + unit: TranscriptJobChargeUnit + amount: float = pydantic.Field() + """ + Credits this purchase charged. 0 when a prior unlock made it free. + """ + + from_: typing_extensions.Annotated[ + typing.Optional[TranscriptJobChargeFrom], + FieldMetadata(alias="from"), + pydantic.Field( + alias="from", + description="Where the credits came from: the included allowance, on-demand usage, or both. Present once the purchase is funded.", + ), + ] = None + """ + Where the credits came from: the included allowance, on-demand usage, or both. Present once the purchase is funded. + """ + + 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/arcmira/types/transcript_job_charge_from.py b/src/arcmira/types/transcript_job_charge_from.py new file mode 100644 index 0000000..54f0939 --- /dev/null +++ b/src/arcmira/types/transcript_job_charge_from.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptJobChargeFrom = typing.Union[typing.Literal["included", "on_demand", "mixed"], typing.Any] diff --git a/src/arcmira/types/transcript_job_charge_unit.py b/src/arcmira/types/transcript_job_charge_unit.py new file mode 100644 index 0000000..0ddd7ab --- /dev/null +++ b/src/arcmira/types/transcript_job_charge_unit.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptJobChargeUnit = typing.Union[typing.Literal["credits"], typing.Any] diff --git a/src/arcmira/types/transcript_job_stage.py b/src/arcmira/types/transcript_job_stage.py new file mode 100644 index 0000000..f2585dc --- /dev/null +++ b/src/arcmira/types/transcript_job_stage.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptJobStage = typing.Union[typing.Literal["queued", "transcribing", "analyzing"], typing.Any] diff --git a/src/arcmira/types/transcript_job_state.py b/src/arcmira/types/transcript_job_state.py new file mode 100644 index 0000000..04a0e9e --- /dev/null +++ b/src/arcmira/types/transcript_job_state.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptJobState = typing.Union[typing.Literal["pending", "ready", "failed", "refunded"], typing.Any] diff --git a/src/arcmira/types/transcript_request_status.py b/src/arcmira/types/transcript_job_status.py similarity index 85% rename from src/arcmira/types/transcript_request_status.py rename to src/arcmira/types/transcript_job_status.py index c8f5c81..b93dd3c 100644 --- a/src/arcmira/types/transcript_request_status.py +++ b/src/arcmira/types/transcript_job_status.py @@ -2,7 +2,7 @@ import typing -TranscriptRequestStatus = typing.Union[ +TranscriptJobStatus = typing.Union[ typing.Literal[ "queued", "downloading", "transcribing", "analyzing", "complete", "failed", "refund_pending", "refunded" ], diff --git a/src/arcmira/types/transcript_pending.py b/src/arcmira/types/transcript_pending.py index cb79487..33f8506 100644 --- a/src/arcmira/types/transcript_pending.py +++ b/src/arcmira/types/transcript_pending.py @@ -4,15 +4,14 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .transcript_pending_premium_job import TranscriptPendingPremiumJob +from .transcript_job import TranscriptJob from .transcript_pending_quality import TranscriptPendingQuality class TranscriptPending(UniversalBaseModel): quality: TranscriptPendingQuality - premium_job: TranscriptPendingPremiumJob - status_url: str - next_poll_seconds: float + video_id: str + job: TranscriptJob if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/arcmira/types/transcript_preparation_required.py b/src/arcmira/types/transcript_preparation_required.py new file mode 100644 index 0000000..5573a59 --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required.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 .transcript_preparation_required_action import TranscriptPreparationRequiredAction +from .transcript_preparation_required_last_attempt import TranscriptPreparationRequiredLastAttempt +from .transcript_preparation_required_quality import TranscriptPreparationRequiredQuality +from .transcript_preparation_required_quote import TranscriptPreparationRequiredQuote + + +class TranscriptPreparationRequired(UniversalBaseModel): + quality: TranscriptPreparationRequiredQuality + video_id: str + quote: typing.Optional[TranscriptPreparationRequiredQuote] = pydantic.Field(default=None) + """ + Null when the video has no known duration or is longer than 12 hours; the POST refuses it for the same reason. + """ + + action: TranscriptPreparationRequiredAction = pydantic.Field() + """ + The one request that prepares Premium from included credits and moves no money. + """ + + last_attempt: typing.Optional[TranscriptPreparationRequiredLastAttempt] = pydantic.Field(default=None) + """ + The most recent failed or refunded purchase of this video, when there is one. + """ + + 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/arcmira/types/transcript_preparation_required_action.py b/src/arcmira/types/transcript_preparation_required_action.py new file mode 100644 index 0000000..c382fff --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_action.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 +from .transcript_preparation_required_action_body import TranscriptPreparationRequiredActionBody +from .transcript_preparation_required_action_method import TranscriptPreparationRequiredActionMethod + + +class TranscriptPreparationRequiredAction(UniversalBaseModel): + """ + The one request that prepares Premium from included credits and moves no money. + """ + + method: TranscriptPreparationRequiredActionMethod + url: str + body: TranscriptPreparationRequiredActionBody + + 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/arcmira/types/transcript_pending_premium_job.py b/src/arcmira/types/transcript_preparation_required_action_body.py similarity index 74% rename from src/arcmira/types/transcript_pending_premium_job.py rename to src/arcmira/types/transcript_preparation_required_action_body.py index 475fe5c..240d660 100644 --- a/src/arcmira/types/transcript_pending_premium_job.py +++ b/src/arcmira/types/transcript_preparation_required_action_body.py @@ -6,11 +6,8 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -class TranscriptPendingPremiumJob(UniversalBaseModel): - job_id: str - status: str - next_poll_seconds: float - eta_seconds: typing.Optional[float] = None +class TranscriptPreparationRequiredActionBody(UniversalBaseModel): + video_id: str if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/arcmira/types/transcript_preparation_required_action_method.py b/src/arcmira/types/transcript_preparation_required_action_method.py new file mode 100644 index 0000000..a85aed9 --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_action_method.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPreparationRequiredActionMethod = typing.Union[typing.Literal["POST"], typing.Any] diff --git a/src/arcmira/types/transcript_request_charge.py b/src/arcmira/types/transcript_preparation_required_last_attempt.py similarity index 62% rename from src/arcmira/types/transcript_request_charge.py rename to src/arcmira/types/transcript_preparation_required_last_attempt.py index d0c9474..cbcf1b6 100644 --- a/src/arcmira/types/transcript_request_charge.py +++ b/src/arcmira/types/transcript_preparation_required_last_attempt.py @@ -4,17 +4,15 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .transcript_request_charge_unit import TranscriptRequestChargeUnit -class TranscriptRequestCharge(UniversalBaseModel): +class TranscriptPreparationRequiredLastAttempt(UniversalBaseModel): """ - Accepted charge units. Present on durable purchases; absent only on legacy requests. + The most recent failed or refunded purchase of this video, when there is one. """ - unit: TranscriptRequestChargeUnit - amount: float - credits_per_row: float + status: str + error: str if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/arcmira/types/transcript_preparation_required_quality.py b/src/arcmira/types/transcript_preparation_required_quality.py new file mode 100644 index 0000000..3365af0 --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_quality.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPreparationRequiredQuality = typing.Union[typing.Literal["premium"], typing.Any] diff --git a/src/arcmira/types/transcript_preparation_required_quote.py b/src/arcmira/types/transcript_preparation_required_quote.py new file mode 100644 index 0000000..c9f9651 --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_quote.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 .transcript_preparation_required_quote_charge import TranscriptPreparationRequiredQuoteCharge + + +class TranscriptPreparationRequiredQuote(UniversalBaseModel): + """ + Null when the video has no known duration or is longer than 12 hours; the POST refuses it for the same reason. + """ + + rows: int = pydantic.Field() + """ + Whole-video row-equivalent price. 0 when a prior unlock makes it free. + """ + + charge: TranscriptPreparationRequiredQuoteCharge + eligible: bool = pydantic.Field() + """ + False when the account cannot buy right now; the POST then answers with the reason. + """ + + max_on_demand_cents: int = pydantic.Field() + """ + On-demand money in whole cents the purchase would need. 0 means included credits cover it. + """ + + 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/arcmira/types/transcript_preparation_required_quote_charge.py b/src/arcmira/types/transcript_preparation_required_quote_charge.py new file mode 100644 index 0000000..9fa132e --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_quote_charge.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcript_preparation_required_quote_charge_from import TranscriptPreparationRequiredQuoteChargeFrom +from .transcript_preparation_required_quote_charge_unit import TranscriptPreparationRequiredQuoteChargeUnit + + +class TranscriptPreparationRequiredQuoteCharge(UniversalBaseModel): + unit: TranscriptPreparationRequiredQuoteChargeUnit + amount: float = pydantic.Field() + """ + Credits the purchase would charge. + """ + + from_: typing_extensions.Annotated[ + TranscriptPreparationRequiredQuoteChargeFrom, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Where those credits would come from at the current balance."), + ] + """ + Where those credits would come from at the current balance. + """ + + 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/arcmira/types/transcript_preparation_required_quote_charge_from.py b/src/arcmira/types/transcript_preparation_required_quote_charge_from.py new file mode 100644 index 0000000..11b348d --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_quote_charge_from.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPreparationRequiredQuoteChargeFrom = typing.Union[ + typing.Literal["included", "on_demand", "mixed"], typing.Any +] diff --git a/src/arcmira/types/transcript_preparation_required_quote_charge_unit.py b/src/arcmira/types/transcript_preparation_required_quote_charge_unit.py new file mode 100644 index 0000000..150d249 --- /dev/null +++ b/src/arcmira/types/transcript_preparation_required_quote_charge_unit.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPreparationRequiredQuoteChargeUnit = typing.Union[typing.Literal["credits"], typing.Any] diff --git a/src/arcmira/types/transcript_purchase_quote.py b/src/arcmira/types/transcript_purchase_quote.py index 55e4cca..71a9602 100644 --- a/src/arcmira/types/transcript_purchase_quote.py +++ b/src/arcmira/types/transcript_purchase_quote.py @@ -6,6 +6,7 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from .transcript_purchase_quote_billing_scope import TranscriptPurchaseQuoteBillingScope from .transcript_purchase_quote_charge import TranscriptPurchaseQuoteCharge +from .transcript_purchase_quote_upgrade import TranscriptPurchaseQuoteUpgrade from .transcript_quote import TranscriptQuote @@ -15,6 +16,11 @@ class TranscriptPurchaseQuote(UniversalBaseModel): billing_scope: TranscriptPurchaseQuoteBillingScope owned: bool eligible: bool + upgrade: typing.Optional[TranscriptPurchaseQuoteUpgrade] = pydantic.Field(default=None) + """ + Present when eligible is false: the plan checkout that can buy this transcript, as a button label and an absolute link. + """ + quote: TranscriptQuote charge: TranscriptPurchaseQuoteCharge credits_per_row: float diff --git a/src/arcmira/types/transcript_purchase_quote_charge.py b/src/arcmira/types/transcript_purchase_quote_charge.py index ae9babc..1738421 100644 --- a/src/arcmira/types/transcript_purchase_quote_charge.py +++ b/src/arcmira/types/transcript_purchase_quote_charge.py @@ -3,13 +3,24 @@ import typing import pydantic +import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcript_purchase_quote_charge_from import TranscriptPurchaseQuoteChargeFrom from .transcript_purchase_quote_charge_unit import TranscriptPurchaseQuoteChargeUnit class TranscriptPurchaseQuoteCharge(UniversalBaseModel): unit: TranscriptPurchaseQuoteChargeUnit amount: float + from_: typing_extensions.Annotated[ + TranscriptPurchaseQuoteChargeFrom, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Where the charge would come from at the current balance."), + ] + """ + Where the charge would come from at the current balance. + """ if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/arcmira/types/transcript_purchase_quote_charge_from.py b/src/arcmira/types/transcript_purchase_quote_charge_from.py new file mode 100644 index 0000000..edd9bbd --- /dev/null +++ b/src/arcmira/types/transcript_purchase_quote_charge_from.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptPurchaseQuoteChargeFrom = typing.Union[typing.Literal["included", "on_demand", "mixed"], typing.Any] diff --git a/src/arcmira/types/transcript_request_quote.py b/src/arcmira/types/transcript_purchase_quote_upgrade.py similarity index 55% rename from src/arcmira/types/transcript_request_quote.py rename to src/arcmira/types/transcript_purchase_quote_upgrade.py index 439c346..a061675 100644 --- a/src/arcmira/types/transcript_request_quote.py +++ b/src/arcmira/types/transcript_purchase_quote_upgrade.py @@ -6,20 +6,13 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -class TranscriptRequestQuote(UniversalBaseModel): +class TranscriptPurchaseQuoteUpgrade(UniversalBaseModel): """ - What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free. + Present when eligible is false: the plan checkout that can buy this transcript, as a button label and an absolute link. """ - quarters: int = pydantic.Field() - """ - Number of 15-minute blocks in the video, ceiling'd, minimum 1. - """ - - rows: int = pydantic.Field() - """ - Total unlock cost in rows: 75 rows per 15-minute block. - """ + label: str + href: str if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/arcmira/types/transcript_request.py b/src/arcmira/types/transcript_request.py deleted file mode 100644 index 9166eb8..0000000 --- a/src/arcmira/types/transcript_request.py +++ /dev/null @@ -1,113 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata -from .transcript_request_charge import TranscriptRequestCharge -from .transcript_request_quote import TranscriptRequestQuote -from .transcript_request_stage import TranscriptRequestStage -from .transcript_request_state import TranscriptRequestState -from .transcript_request_status import TranscriptRequestStatus - - -class TranscriptRequest(UniversalBaseModel): - id: typing.Optional[str] = pydantic.Field(default=None) - """ - Transcription request id (UUID). Null only in the degenerate submit response for a video you already own that has no request history. - """ - - video_id: typing_extensions.Annotated[ - str, - FieldMetadata(alias="videoId"), - pydantic.Field(alias="videoId", description="YouTube video id (11 characters)."), - ] - """ - YouTube video id (11 characters). - """ - - status: TranscriptRequestStatus = pydantic.Field() - """ - Request status. Values: queued (accepted; audio download not started), downloading (fetching the video audio), transcribing (premium speech-to-text is running), analyzing (entity/commercial analysis is running), complete (premium transcript is servable via GET /v1/transcripts/{video_id}), failed (rejected intent or legacy purchase requiring accounting review), refund_pending (refund transaction must still complete), refunded (terminal failure; the charged rows were returned and the unlock this submission bought was revoked). - """ - - state: TranscriptRequestState - charge: typing.Optional[TranscriptRequestCharge] = pydantic.Field(default=None) - """ - Accepted charge units. Present on durable purchases; absent only on legacy requests. - """ - - stage: typing.Optional[TranscriptRequestStage] = pydantic.Field(default=None) - """ - User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses. - """ - - quote: TranscriptRequestQuote = pydantic.Field() - """ - What this request charged: rows and 15-minute blocks. rows is 0 when a prior unlock made the submission free. - """ - - eta_seconds: typing_extensions.Annotated[ - typing.Optional[int], - FieldMetadata(alias="etaSeconds"), - pydantic.Field( - alias="etaSeconds", - description="Estimated SECONDS until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight.", - ), - ] = None - """ - Estimated SECONDS until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight. - """ - - next_poll_seconds: typing_extensions.Annotated[ - typing.Optional[int], - FieldMetadata(alias="nextPollSeconds"), - pydantic.Field( - alias="nextPollSeconds", - description="Recommended SECONDS to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight.", - ), - ] = None - """ - Recommended SECONDS to sleep before the next poll (also sent as the Retry-After header). Only present while the request is in flight. - """ - - error: typing.Optional[str] = pydantic.Field(default=None) - """ - Failure reason. Only present when status is failed or refunded. - """ - - refunded: typing.Optional[bool] = pydantic.Field(default=None) - """ - True when the charged rows were returned. Only present when status is failed or refunded. - """ - - created_at: typing_extensions.Annotated[ - str, - FieldMetadata(alias="createdAt"), - pydantic.Field(alias="createdAt", description="When the request was submitted."), - ] - """ - When the request was submitted. - """ - - completed_at: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="completedAt"), - pydantic.Field( - alias="completedAt", description="When the request reached a terminal status. Absent while in flight." - ), - ] = None - """ - When the request reached a terminal status. Absent while in flight. - """ - - 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/arcmira/types/transcript_request_charge_unit.py b/src/arcmira/types/transcript_request_charge_unit.py deleted file mode 100644 index 8fd41ff..0000000 --- a/src/arcmira/types/transcript_request_charge_unit.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -TranscriptRequestChargeUnit = typing.Union[typing.Literal["rows", "credits"], typing.Any] diff --git a/src/arcmira/types/transcript_request_list_response_requests_item.py b/src/arcmira/types/transcript_request_list_response_requests_item.py index 8ff1494..1014f22 100644 --- a/src/arcmira/types/transcript_request_list_response_requests_item.py +++ b/src/arcmira/types/transcript_request_list_response_requests_item.py @@ -4,10 +4,10 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2 -from .transcript_request import TranscriptRequest +from .transcript_job import TranscriptJob -class TranscriptRequestListResponseRequestsItem(TranscriptRequest): +class TranscriptRequestListResponseRequestsItem(TranscriptJob): title: typing.Optional[str] = pydantic.Field(default=None) """ Video title for display. Null when unknown. diff --git a/src/arcmira/types/transcript_request_stage.py b/src/arcmira/types/transcript_request_stage.py deleted file mode 100644 index 8d10839..0000000 --- a/src/arcmira/types/transcript_request_stage.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -TranscriptRequestStage = typing.Union[typing.Literal["queued", "transcribing", "analyzing"], typing.Any] diff --git a/src/arcmira/types/transcript_request_state.py b/src/arcmira/types/transcript_request_state.py deleted file mode 100644 index d43d8ee..0000000 --- a/src/arcmira/types/transcript_request_state.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -TranscriptRequestState = typing.Union[typing.Literal["pending", "ready", "failed", "refunded"], typing.Any] diff --git a/src/arcmira/types/transcript_request_submit_response.py b/src/arcmira/types/transcript_request_submit_response.py index cf71534..16932a7 100644 --- a/src/arcmira/types/transcript_request_submit_response.py +++ b/src/arcmira/types/transcript_request_submit_response.py @@ -3,29 +3,15 @@ import typing import pydantic -import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata -from .transcript_request import TranscriptRequest +from .transcript_job import TranscriptJob class TranscriptRequestSubmitResponse(UniversalBaseModel): - request: TranscriptRequest - existing: typing.Optional[bool] = pydantic.Field(default=None) + job: TranscriptJob + existing: bool = pydantic.Field() """ - True when an in-flight (or already-satisfied) request for the same video was returned instead of creating a new one. - """ - - over_limit: typing_extensions.Annotated[ - typing.Optional[bool], - FieldMetadata(alias="overLimit"), - pydantic.Field( - alias="overLimit", - description="Only present (true) when this purchase consumed the rest of the included row allocation.", - ), - ] = None - """ - Only present (true) when this purchase consumed the rest of the included row allocation. + True when a request for this video already existed (in flight or ready) and was returned instead of creating a new one. """ if IS_PYDANTIC_V2: diff --git a/src/arcmira/types/transcript_response.py b/src/arcmira/types/transcript_response.py index ad4dabc..3d0fbaf 100644 --- a/src/arcmira/types/transcript_response.py +++ b/src/arcmira/types/transcript_response.py @@ -5,10 +5,10 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from .caption_track import CaptionTrack +from .transcript_job import TranscriptJob from .transcript_response_access import TranscriptResponseAccess from .transcript_response_lines_item import TranscriptResponseLinesItem from .transcript_response_paragraphs_item import TranscriptResponseParagraphsItem -from .transcript_response_premium_job import TranscriptResponsePremiumJob from .transcript_response_quality import TranscriptResponseQuality from .transcript_response_range import TranscriptResponseRange from .transcript_response_source import TranscriptResponseSource @@ -73,11 +73,7 @@ class TranscriptResponse(UniversalBaseModel): When the transcript was produced. """ - premium_job: typing.Optional[TranscriptResponsePremiumJob] = pydantic.Field(default=None) - """ - Reserved for job metadata. Pending Premium retrieval uses its separate 202 response. - """ - + premium_job: typing.Optional[TranscriptJob] = None access: typing.Optional[TranscriptResponseAccess] = pydantic.Field(default=None) """ The gate that reduced this response. Present only when something was withheld; carries the same code, gate, and unlock an outright refusal would. diff --git a/src/arcmira/types/transcript_response_access.py b/src/arcmira/types/transcript_response_access.py index c32adf5..45d7306 100644 --- a/src/arcmira/types/transcript_response_access.py +++ b/src/arcmira/types/transcript_response_access.py @@ -55,6 +55,16 @@ class TranscriptResponseAccess(UniversalBaseModel): Present on rate gates. Mirrors the Retry-After header. """ + current_revision: typing.Optional[str] = pydantic.Field(default=None) + """ + On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction. + """ + + expected_seq: typing.Optional[int] = pydantic.Field(default=None) + """ + On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key. + """ + doc_url: str request_id: str diff --git a/src/arcmira/types/transcript_response_premium_job.py b/src/arcmira/types/transcript_response_premium_job.py deleted file mode 100644 index 79fffe7..0000000 --- a/src/arcmira/types/transcript_response_premium_job.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 TranscriptResponsePremiumJob(UniversalBaseModel): - """ - Reserved for job metadata. Pending Premium retrieval uses its separate 202 response. - """ - - job_id: typing.Optional[str] = pydantic.Field(default=None) - """ - Transcription request id. Poll it with GET /v1/transcriptions/{id}. - """ - - status: str = pydantic.Field() - """ - Pipeline status at submit time. - """ - - next_poll_seconds: typing.Optional[int] = pydantic.Field(default=None) - """ - Seconds to wait before polling again. - """ - - eta_seconds: typing.Optional[int] = pydantic.Field(default=None) - """ - Estimated seconds until the Premium transcript is ready. - """ - - 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/arcmira/types/transcript_result.py b/src/arcmira/types/transcript_result.py index d5a2ec0..62b56bd 100644 --- a/src/arcmira/types/transcript_result.py +++ b/src/arcmira/types/transcript_result.py @@ -8,12 +8,15 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from .caption_track import CaptionTrack -from .transcript_pending_premium_job import TranscriptPendingPremiumJob +from .transcript_job import TranscriptJob from .transcript_pending_quality import TranscriptPendingQuality +from .transcript_preparation_required_action import TranscriptPreparationRequiredAction +from .transcript_preparation_required_last_attempt import TranscriptPreparationRequiredLastAttempt +from .transcript_preparation_required_quality import TranscriptPreparationRequiredQuality +from .transcript_preparation_required_quote import TranscriptPreparationRequiredQuote from .transcript_response_access import TranscriptResponseAccess from .transcript_response_lines_item import TranscriptResponseLinesItem from .transcript_response_paragraphs_item import TranscriptResponseParagraphsItem -from .transcript_response_premium_job import TranscriptResponsePremiumJob from .transcript_response_quality import TranscriptResponseQuality from .transcript_response_range import TranscriptResponseRange from .transcript_response_source import TranscriptResponseSource @@ -35,7 +38,7 @@ class TranscriptResult_Ready(UniversalBaseModel): range: typing.Optional[TranscriptResponseRange] = None rows_billed: int as_of: typing.Optional[str] = None - premium_job: typing.Optional[TranscriptResponsePremiumJob] = None + premium_job: typing.Optional[TranscriptJob] = None access: typing.Optional[TranscriptResponseAccess] = None note: str @@ -49,12 +52,29 @@ class Config: extra = pydantic.Extra.allow +class TranscriptResult_PreparationRequired(UniversalBaseModel): + state: typing.Literal["preparation_required"] = "preparation_required" + quality: TranscriptPreparationRequiredQuality + video_id: str + quote: typing.Optional[TranscriptPreparationRequiredQuote] = None + action: TranscriptPreparationRequiredAction + last_attempt: typing.Optional[TranscriptPreparationRequiredLastAttempt] = 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 + + class TranscriptResult_Pending(UniversalBaseModel): state: typing.Literal["pending"] = "pending" quality: TranscriptPendingQuality - premium_job: TranscriptPendingPremiumJob - status_url: str - next_poll_seconds: float + video_id: str + job: TranscriptJob if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 @@ -67,5 +87,6 @@ class Config: TranscriptResult = typing_extensions.Annotated[ - typing.Union[TranscriptResult_Ready, TranscriptResult_Pending], pydantic.Field(discriminator="state") + typing.Union[TranscriptResult_Ready, TranscriptResult_PreparationRequired, TranscriptResult_Pending], + pydantic.Field(discriminator="state"), ] diff --git a/src/arcmira/types/transcript_search_response_access.py b/src/arcmira/types/transcript_search_response_access.py index 1029026..71beb20 100644 --- a/src/arcmira/types/transcript_search_response_access.py +++ b/src/arcmira/types/transcript_search_response_access.py @@ -55,6 +55,16 @@ class TranscriptSearchResponseAccess(UniversalBaseModel): Present on rate gates. Mirrors the Retry-After header. """ + current_revision: typing.Optional[str] = pydantic.Field(default=None) + """ + On revision_mismatch and anchor_mismatch, the transcript revision to re-read before re-anchoring the correction. + """ + + expected_seq: typing.Optional[int] = pydantic.Field(default=None) + """ + On sequence_mismatch (HTTP 412), the seq the server expects next for this video. Rebase local counters onto it and resend under the same key. + """ + doc_url: str request_id: str diff --git a/tests/fixtures/transcription-responses.json b/tests/fixtures/transcription-responses.json index 4fdfae1..7f7b574 100644 --- a/tests/fixtures/transcription-responses.json +++ b/tests/fixtures/transcription-responses.json @@ -133,14 +133,15 @@ "duration_seconds": 600, "billing_scope": "full_video", "owned": false, - "eligible": false, + "eligible": true, "quote": { "quarters": 1, "rows": 75 }, "charge": { - "unit": "rows", - "amount": 75 + "unit": "credits", + "amount": 300, + "from": "included" }, "credits_per_row": 4, "max_on_demand_cents": 0, @@ -156,14 +157,15 @@ "duration_seconds": 600, "billing_scope": "full_video", "owned": false, - "eligible": false, + "eligible": true, "quote": { "quarters": 1, "rows": 75 }, "charge": { - "unit": "rows", - "amount": 75 + "unit": "credits", + "amount": 300, + "from": "included" }, "credits_per_row": 4, "max_on_demand_cents": 0, @@ -178,36 +180,19 @@ "status": 200, "body": { "id": "00000000-0000-4000-8000-000000000001", - "videoId": "dQw4w9WgXcQ", - "status": "queued", + "video_id": "dQw4w9WgXcQ", "state": "pending", + "status": "queued", "stage": "queued", - "quote": { - "quarters": 1, - "rows": 75 + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" }, - "etaSeconds": 172, - "nextPollSeconds": 29, - "createdAt": "2026-10-01 00:00:00" - }, - "responses": { - "200": { - "status": 200, - "body": { - "id": "00000000-0000-4000-8000-000000000001", - "videoId": "dQw4w9WgXcQ", - "status": "queued", - "state": "pending", - "stage": "queued", - "quote": { - "quarters": 1, - "rows": 75 - }, - "etaSeconds": 172, - "nextPollSeconds": 29, - "createdAt": "2026-10-01 00:00:00" - } - } + "eta_seconds": 172, + "next_poll_seconds": 29, + "created_at": "2026-10-01 00:00:00", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001" } }, "list_transcriptions": { @@ -216,48 +201,209 @@ "requests": [ { "id": "00000000-0000-4000-8000-000000000001", - "videoId": "dQw4w9WgXcQ", - "status": "queued", + "video_id": "dQw4w9WgXcQ", "state": "pending", + "status": "queued", "stage": "queued", - "quote": { - "quarters": 1, - "rows": 75 + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" }, - "etaSeconds": 172, - "nextPollSeconds": 29, - "createdAt": "2026-10-01 00:00:00", + "eta_seconds": 172, + "next_poll_seconds": 29, + "created_at": "2026-10-01 00:00:00", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001", "title": "First video" } ], "has_more": false, "next_cursor": null - }, - "responses": { - "200": { - "status": 200, + } + }, + "preparation_required": { + "status": 200, + "body": { + "state": "preparation_required", + "quality": "premium", + "video_id": "dQw4w9WgXcQ", + "quote": { + "rows": 300, + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" + }, + "eligible": true, + "max_on_demand_cents": 0 + }, + "action": { + "method": "POST", + "url": "https://api.arcmira.com/v1/transcriptions", "body": { - "requests": [ - { - "id": "00000000-0000-4000-8000-000000000001", - "videoId": "dQw4w9WgXcQ", - "status": "queued", - "state": "pending", - "stage": "queued", - "quote": { - "quarters": 1, - "rows": 75 - }, - "etaSeconds": 172, - "nextPollSeconds": 29, - "createdAt": "2026-10-01 00:00:00", - "title": "First video" - } - ], - "has_more": false, - "next_cursor": null + "video_id": "dQw4w9WgXcQ" } + }, + "last_attempt": { + "status": "refunded", + "error": "Transcription timed out." + } + } + }, + "pending_premium": { + "status": 200, + "body": { + "state": "pending", + "quality": "premium", + "video_id": "dQw4w9WgXcQ", + "job": { + "id": "00000000-0000-4000-8000-000000000001", + "video_id": "dQw4w9WgXcQ", + "state": "pending", + "status": "queued", + "stage": "queued", + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" + }, + "eta_seconds": 172, + "next_poll_seconds": 29, + "created_at": "2026-10-01 00:00:00", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001" } } + }, + "submit_pending": { + "status": 200, + "body": { + "job": { + "id": "00000000-0000-4000-8000-000000000001", + "video_id": "dQw4w9WgXcQ", + "state": "pending", + "status": "queued", + "stage": "queued", + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" + }, + "eta_seconds": 172, + "next_poll_seconds": 29, + "created_at": "2026-10-01 00:00:00", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001" + }, + "existing": false + } + }, + "submit_ready": { + "status": 200, + "body": { + "job": { + "id": "00000000-0000-4000-8000-000000000001", + "video_id": "dQw4w9WgXcQ", + "state": "ready", + "status": "complete", + "stage": null, + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" + }, + "created_at": "2026-10-01 00:00:00", + "completed_at": "2026-10-01 00:04:10", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001" + }, + "existing": true + } + }, + "job_ready": { + "status": 200, + "body": { + "id": "00000000-0000-4000-8000-000000000001", + "video_id": "dQw4w9WgXcQ", + "state": "ready", + "status": "complete", + "stage": null, + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" + }, + "created_at": "2026-10-01 00:00:00", + "completed_at": "2026-10-01 00:04:10", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001" + } + }, + "job_refunded": { + "status": 200, + "body": { + "id": "00000000-0000-4000-8000-000000000001", + "video_id": "dQw4w9WgXcQ", + "state": "refunded", + "status": "refunded", + "stage": null, + "charge": { + "unit": "credits", + "amount": 1200, + "from": "included" + }, + "created_at": "2026-10-01 00:00:00", + "status_url": "https://api.arcmira.com/v1/transcriptions/00000000-0000-4000-8000-000000000001", + "error": "Transcription timed out.", + "refunded": true, + "completed_at": "2026-10-01 00:04:10" + } + }, + "premium_ready": { + "status": 200, + "body": { + "state": "ready", + "video": { + "id": "dQw4w9WgXcQ", + "title": "TBPN | Tuesday, August 4", + "channel_id": "UC-DRzaGnL_vtBUpCFH5M0tg", + "channel_name": "TBPN", + "published_at": "2026-08-04T17:00:00.000Z", + "duration_seconds": 3600, + "watch_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + "quality": "premium", + "source": "arcmira_premium", + "language": "en", + "languages": [], + "lines": [ + { + "start": 0, + "end": 20, + "text": "Ramp has been on the show for a while now.", + "speaker": 0 + }, + { + "start": 20, + "end": 45, + "text": "The pitch is still the same, spend less time on expenses.", + "speaker": 1 + } + ], + "speakers": [ + { + "id": 0, + "name": "John Coogan", + "entity_id": 91, + "confidence": "high" + }, + { + "id": 1, + "name": "Speaker 1", + "entity_id": null, + "confidence": null + } + ], + "revision": "rev_1", + "rows_billed": 0, + "as_of": "2026-08-05T00:00:00.000Z", + "note": "Premium transcripts are Arcmira's own. Every Premium transcript is diarized: each line carries a speaker. We identify each speaker from the audio, the video, and internal and community review. We are right most of the time and wrong sometimes, so when a name matters, say it came from Arcmira's speaker identification and cite the line." + } } } diff --git a/tests/test_generation.py b/tests/test_generation.py index fdbd33c..4df806f 100644 --- a/tests/test_generation.py +++ b/tests/test_generation.py @@ -20,15 +20,19 @@ def test_actual_contract_generates_union_key_and_collections(self): self.assertEqual(doc, before) union = prepared['components']['schemas']['TranscriptResult'] self.assertEqual(union['discriminator']['propertyName'], 'state') - self.assertEqual(set(union['discriminator']['mapping']), {'ready','pending'}) + self.assertEqual(set(union['discriminator']['mapping']), {'ready','preparation_required','pending'}) for path, collection in [('/v1/transcriptions','requests'),('/v1/channels/{channel_id}/videos','episodes')]: self.assertEqual(prepared['paths'][path]['get']['x-fern-pagination']['results'], '$response.'+collection) post = prepared['paths']['/v1/transcriptions']['post'] - self.assertTrue(next(p for p in post['parameters'] if p['name']=='Idempotency-Key')['required']) - self.assertIn('max_rows', post['requestBody']['content']['application/json']['schema']['required']) + self.assertFalse(next(p for p in post['parameters'] if p['name']=='Idempotency-Key').get('required')) + body = post['requestBody']['content']['application/json']['schema'] + self.assertNotIn('videoId', body['properties']) + self.assertNotIn('max_rows', body.get('required', [])) + self.assertEqual(post['x-fern-sdk-method-name'], 'request') def test_installed_api_error_matches_the_preserved_override(self): self.assertEqual((ROOT / 'src/arcmira/core/api_error.py').read_text(), (ROOT / 'scripts/overrides/api_error.py').read_text()) self.assertIn('overrides/api_error.py', (ROOT / 'scripts/install-generated.py').read_text()) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_transcription.py b/tests/test_transcription.py index 0ead1bf..0232ede 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -19,7 +19,7 @@ def error(code, kind): PAGE_CAP = 5 CURSOR = 'signed+/opaque==&cursor' -PENDING = dict(state='pending', quality='premium', premium_job=dict(job_id=REQUEST['id'], status='queued', next_poll_seconds=5), status_url='/v1/transcriptions/'+REQUEST['id'], next_poll_seconds=5) +PENDING = FIXTURES['pending_premium']['body'] CALLS = [] RECEIPTS = {} @@ -44,14 +44,12 @@ def answer(self): elif url.path == '/v1/transcriptions' and self.command == 'POST': key = self.headers['Idempotency-Key'] replay = key in RECEIPTS - if key is None: - status, result = 400, {'error': error('idempotency_key_required', 'invalid_request_error')} - elif replay and RECEIPTS[key] != body: + if replay and RECEIPTS[key] != body: status, result = 409, {'error': error('idempotency_conflict', 'conflict_error')} else: RECEIPTS[key] = body status = 200 if replay else 202 - result = {'request': REQUEST, **({'existing': True} if replay else {})} + result = {'job': REQUEST, 'existing': replay} if replay: extra['Idempotency-Replayed'] = 'true' elif url.path == '/v1/transcriptions': result = {'requests': [{**REQUEST, 'id': 'request-2' if 'cursor' in query else 'request-1'}], 'has_more': 'cursor' not in query, 'next_cursor': None if 'cursor' in query else CURSOR} @@ -92,7 +90,7 @@ def test_ready_pending_status_and_union(self): pending = self.client.transcripts.with_raw_response.get(video_id='pending0000', quality='premium') self.assertIsInstance(pending.data, TranscriptResult_Pending) self.assertEqual(pending.status_code, 202) - self.assertEqual(pending.data.status_url, PENDING['status_url']) + self.assertEqual(pending.data.job.status_url, PENDING['job']['status_url']) self.assertEqual(pending.headers['retry-after'], '5') def test_quote_and_refusal(self): @@ -105,7 +103,7 @@ def test_quote_and_refusal(self): self.assertEqual(str(caught.exception), '403 purchase_required: purchase_required') self.assertNotIn('headers', str(caught.exception)) - def test_preparation_exact_intent_replay_and_required_key(self): + def test_preparation_exact_intent_and_replay(self): intent = dict(video_id='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0, idempotency_key='python-saved-intent') first = self.client.transcripts.with_raw_response.request(**intent) replay = self.client.transcripts.with_raw_response.request(**intent) @@ -113,15 +111,13 @@ def test_preparation_exact_intent_replay_and_required_key(self): self.assertEqual(sent[-2:], ['python-saved-intent', 'python-saved-intent']) self.assertEqual(first.status_code, 202) self.assertEqual(replay.status_code, 200) - self.assertEqual(replay.data.request.id, first.data.request.id) + self.assertEqual(replay.data.job.id, first.data.job.id) self.assertTrue(replay.data.existing) self.assertEqual(replay.headers['idempotency-replayed'], 'true') - self.assertEqual(json.loads(CALLS[-1][3]), dict(videoId='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0)) + self.assertEqual(json.loads(CALLS[-1][3]), dict(video_id='dQw4w9WgXcQ', max_rows=300, max_on_demand_cents=0)) with self.assertRaises(ApiError) as caught: self.client.transcripts.request(**{**intent, 'max_rows':600}) self.assertEqual(caught.exception.status_code, 409) - with self.assertRaises(TypeError): - self.client.transcripts.request(video_id='dQw4w9WgXcQ', max_rows=300) def test_request_and_episode_arrays_preserve_opaque_cursor(self): before = len(CALLS) From 798e050d921af8eb182d94ad8e0aef9752b5745e Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Fri, 2 Oct 2026 00:44:20 -0700 Subject: [PATCH 09/12] Add prepare_and_wait for Premium transcripts Reads Premium, posts the purchase once when preparation is required, polls the job at the pace the API asks for and returns the transcript. The code lives in scripts/overrides and the installer wires it into the generated clients, so regeneration keeps it. The README leads with it. --- README.md | 64 +++++++---- scripts/install-generated.py | 13 ++- scripts/overrides/prepare.py | 179 +++++++++++++++++++++++++++++ src/arcmira/__init__.py | 1 + src/arcmira/transcripts/client.py | 5 +- src/arcmira/transcripts/prepare.py | 179 +++++++++++++++++++++++++++++ 6 files changed, 415 insertions(+), 26 deletions(-) create mode 100644 scripts/overrides/prepare.py create mode 100644 src/arcmira/transcripts/prepare.py diff --git a/README.md b/README.md index 2a9a80f..d21bca1 100644 --- a/README.md +++ b/README.md @@ -8,47 +8,65 @@ pip install arcmira Set `ARCMIRA_API_KEY` or pass `api_key` to the client. +## Premium quickstart + ```python from arcmira import Arcmira -client = Arcmira() -quote = client.transcripts.quote(video_id="dQw4w9WgXcQ") -print(quote.quote.rows, quote.charge) +transcript = Arcmira().transcripts.prepare_and_wait("dQw4w9WgXcQ") +print(transcript.lines) ``` -A quote is free. Preparation purchases the whole video, even if you later read a short window. Persist the intent before submitting it. Choose ceilings after reviewing the quote and authorizing the cost. +`prepare_and_wait(video_id, *, max_on_demand_cents=0, timeout_seconds=300)` reads the Premium transcript and returns it. When your account does not own it yet, the call posts `{video_id}` to `/v1/transcriptions` once, polls the job at the pace the API sets with `Retry-After` and `next_poll_seconds`, and reads the finished transcript. The default spends included credits only and sends no `Idempotency-Key`. Preparation purchases the whole video, even if you only read a window. + +`AsyncArcmira` has the same method: `await client.transcripts.prepare_and_wait(video_id)`. + +Errors you can handle: + +- `PreparationTimeoutError` when the job outlasts `timeout_seconds`. It carries `.job`, and the job keeps running. +- `PreparationFailedError` when the job fails or is refunded. It carries `.job`. +- `PremiumUnavailableError` when the plan has no Premium. The read returned captions, and they are never returned as Premium. +- `ApiError` for any API refusal, such as `quota_exceeded`, with the public error and recovery URLs in `body`. + +To spend money, pass a cents ceiling you have approved. The call then sends the quoted `max_rows` and a generated `Idempotency-Key`. ```python -intent = dict( - video_id="dQw4w9WgXcQ", - max_rows=300, - max_on_demand_cents=0, - idempotency_key="saved-order-dQw4w9WgXcQ-1", -) -order = client.transcripts.with_raw_response.request(**intent) -print(order.status_code, order.data.request.state) +client = Arcmira() +transcript = client.transcripts.prepare_and_wait("dQw4w9WgXcQ", max_on_demand_cents=25) ``` -`idempotency_key` and `max_rows` are required. `max_on_demand_cents` defaults to zero. If the response is lost, retry with the same saved key and exact input. The response retains `{request, existing?}` and the `Idempotency-Replayed` header. A changed intent with the same key returns `409 idempotency_conflict`. +## Lower-level calls -GET never purchases Premium. A ready read includes transcript lines. A pending read includes a status URL and polling delay. +A quote is free. GET never purchases Premium, and each state is typed. ```python -result = client.transcripts.with_raw_response.get( - video_id="dQw4w9WgXcQ", quality="premium" -) +client = Arcmira() +quote = client.transcripts.quote(video_id="dQw4w9WgXcQ") +print(quote.quote.rows, quote.charge) + +result = client.transcripts.with_raw_response.get(video_id="dQw4w9WgXcQ", quality="premium") if result.data.state == "ready": print(result.data.lines) +elif result.data.state == "preparation_required": + print(result.data.quote, result.data.action) else: - print(result.data.status_url, result.data.next_poll_seconds) + print(result.data.job.status_url, result.data.job.next_poll_seconds) print(result.status_code, result.headers) ``` -A refusal raises a typed error derived from `arcmira.core.api_error.ApiError`. Its `body` retains the public error, quote, and recovery URLs. `refund_pending` is unfinished; a refund is complete only when the request reports `refunded`. +To prepare without waiting, post the purchase and poll the job yourself. Every purchase answers one `Job`. `idempotency_key` is optional while `max_on_demand_cents` is 0: without one, a purchase already open or owned for the video is returned with `existing: true`. `max_on_demand_cents` defaults to zero. A positive amount requires both `idempotency_key` and `max_rows`, and a retry with the same saved key and exact input returns the same job with the `Idempotency-Replayed` header. + +```python +order = client.transcripts.with_raw_response.request(video_id="dQw4w9WgXcQ") +print(order.status_code, order.data.job.state) +job = client.transcripts.status(order.data.job.id) +``` + +A refusal raises a typed error derived from `arcmira.core.api_error.ApiError`. Its `body` retains the public error, quote, and recovery URLs. `refund_pending` is unfinished; a refund is complete only when the job reports `refunded`. ```python -for request in client.transcripts.list_requests(limit=10): - print(request.id, request.state) +for job in client.transcripts.list_requests(limit=10): + print(job.id, job.state) for episode in client.channels.videos.list(channel_id="UC-DRzaGnL_vtBUpCFH5M0tg", limit=10): print(episode.video_id) ``` @@ -70,7 +88,7 @@ See the [generated reference](reference.md) for all endpoints. ## Regenerate and verify -Run `bash scripts/generate.sh` with Node 22 or newer, Python 3, Docker, and Fern access for the `arcmira` organization. Generation pins Fern CLI 5.131.1 and Python generator 5.31.0, disables CLI version redirection and telemetry, and reads `fern/openapi.json`. The overlay combines distinct success schemas and rejects unknown or ambiguous cursor collections. Generated source is never edited by hand. +Run `bash scripts/generate.sh` with Node 22 or newer, Python 3, Docker, and Fern access for the `arcmira` organization. Generation pins Fern CLI 5.131.1 and Python generator 5.31.0, disables CLI version redirection and telemetry, and reads `fern/openapi.json`. The overlay combines distinct success schemas and rejects unknown or ambiguous cursor collections. Generated source is never edited by hand. `scripts/overrides/` holds the hand-written pieces the installer copies back after every generation: the `ApiError` text and `prepare_and_wait`. ```sh uv sync @@ -78,7 +96,7 @@ uv run python -m unittest discover -s tests -v uv build ``` -The tests use a local HTTP server. They check both client variants, state discrimination, response status, quotes, refusals, exact replay input, and opaque pagination. No live API key or purchase is required. +The tests use a local HTTP server that returns the bodies the API sends. They check both client variants, state discrimination, `prepare_and_wait`, response status, quotes, refusals, replay input, and opaque pagination. No live API key or purchase is required. Version 0.3.0 replaces the earlier URL-only placeholder with a usable SDK. The public URL constants remain available. diff --git a/scripts/install-generated.py b/scripts/install-generated.py index 74b63e0..79dacbf 100644 --- a/scripts/install-generated.py +++ b/scripts/install-generated.py @@ -17,11 +17,22 @@ generated_error = (target / 'core/api_error.py').read_text() assert 'class ApiError(Exception)' in generated_error (target / 'core/api_error.py').write_text((root / 'scripts/overrides/api_error.py').read_text()) +# prepare_and_wait is hand-written; the generated transcripts clients inherit its mixins. +(target / 'transcripts/prepare.py').write_text((root / 'scripts/overrides/prepare.py').read_text()) +transcripts_client = target / 'transcripts/client.py' +text = transcripts_client.read_text() +for name, mixin in (('TranscriptsClient', 'PrepareAndWait'), ('AsyncTranscriptsClient', 'AsyncPrepareAndWait')): + declaration = f'class {name}:\n' + assert text.count(declaration) == 1 + text = text.replace(declaration, f'class {name}({mixin}):\n') +anchor_import = 'from .raw_client import' +assert text.count(anchor_import) == 1 +transcripts_client.write_text(text.replace(anchor_import, 'from .prepare import AsyncPrepareAndWait, PrepareAndWait\n' + anchor_import)) # The previous public pointer package exported these constants. init = target / '__init__.py' text = init.read_text() assert text.startswith('# This file was auto-generated by Fern') -init.write_text(text + '\nfrom ._package import __version__, homepage, docs, api_base, openapi, llms_txt, docs_llms_txt\n') +init.write_text(text + '\nfrom ._package import __version__, homepage, docs, api_base, openapi, llms_txt, docs_llms_txt\nfrom .transcripts.prepare import PreparationError, PreparationFailedError, PreparationTimeoutError, PremiumUnavailableError\n') version = (root / 'VERSION').read_text().strip() (target / '_package.py').write_text(f'''# Written by scripts/install-generated.py from VERSION. __version__ = {version!r} diff --git a/scripts/overrides/prepare.py b/scripts/overrides/prepare.py new file mode 100644 index 0000000..2d0770a --- /dev/null +++ b/scripts/overrides/prepare.py @@ -0,0 +1,179 @@ +# Maintained by hand. scripts/install-generated.py copies this to +# src/arcmira/transcripts/prepare.py and makes the generated transcripts +# clients inherit these mixins, so regeneration keeps prepare_and_wait. + +from __future__ import annotations + +import asyncio +import time +import typing +import uuid + +from ..types.transcript_job import TranscriptJob +from ..types.transcript_result import TranscriptResult_Ready + +if typing.TYPE_CHECKING: + from .client import AsyncTranscriptsClient, TranscriptsClient + +DEFAULT_POLL_SECONDS = 5 + + +class PreparationError(Exception): + """Base class for prepare_and_wait refusals that are not HTTP errors.""" + + +class PreparationTimeoutError(PreparationError): + """The Premium job was still pending when timeout_seconds ran out. + + The job keeps running. Read the transcript later, or poll `job.status_url`. + """ + + def __init__(self, job: TranscriptJob) -> None: + super().__init__(f"Premium job {job.id} for {job.video_id} is still {job.status}") + self.job = job + + +class PreparationFailedError(PreparationError): + """The Premium job ended failed or refunded, or finished without a servable transcript.""" + + def __init__(self, job: TranscriptJob) -> None: + super().__init__(f"Premium job {job.id} for {job.video_id} ended {job.state}: {job.error or job.status}") + self.job = job + + +class PremiumUnavailableError(PreparationError): + """The account was served captions instead of Premium, so nothing can be prepared.""" + + def __init__(self, transcript: TranscriptResult_Ready) -> None: + super().__init__(f"Premium is not available for this account; the read returned {transcript.quality}") + self.transcript = transcript + + +# One step of the plan. The sync and async clients run the same plan, so +# they cannot drift on when to submit, when to poll, or when to give up. +_READ, _SUBMIT, _STATUS, _SLEEP = "read", "submit", "status", "sleep" +_Step = typing.Tuple[str, typing.Any] + + +def _delay(response: typing.Any, job: TranscriptJob) -> float: + header = response.response.headers.get("retry-after") + if header is not None and header.strip().isdigit(): + return float(header) + return float(job.next_poll_seconds if job.next_poll_seconds is not None else DEFAULT_POLL_SECONDS) + + +def _owned(transcript: typing.Any) -> TranscriptResult_Ready: + if transcript.quality != "premium": + raise PremiumUnavailableError(transcript) + return transcript + + +def _plan( + video_id: str, max_on_demand_cents: int, timeout_seconds: float +) -> typing.Generator[_Step, typing.Any, TranscriptResult_Ready]: + if max_on_demand_cents < 0: + raise ValueError("max_on_demand_cents cannot be negative") + deadline = time.monotonic() + timeout_seconds + read = yield (_READ, None) + transcript = read.data + if transcript.state == "ready": + return _owned(transcript) + if transcript.state == "pending": + response, job = read, transcript.job + else: + submission: typing.Dict[str, typing.Any] = {"video_id": video_id} + if max_on_demand_cents > 0: + # Spending money is the only case that needs a key; one key serves this call and its own retries. + submission.update(max_on_demand_cents=max_on_demand_cents, idempotency_key=str(uuid.uuid4())) + if transcript.quote is not None: + submission["max_rows"] = transcript.quote.rows + response = yield (_SUBMIT, submission) + job = response.data.job + while job.state == "pending": + remaining = deadline - time.monotonic() + if remaining <= 0: + raise PreparationTimeoutError(job) + yield (_SLEEP, min(_delay(response, job), remaining)) + response = yield (_STATUS, job.id) + job = response.data + if job.state != "ready": + raise PreparationFailedError(job) + final = (yield (_READ, None)).data + if final.state != "ready": + raise PreparationFailedError(job) + return _owned(final) + + +class PrepareAndWait: + def prepare_and_wait( + self: TranscriptsClient, + video_id: str, + *, + max_on_demand_cents: int = 0, + timeout_seconds: float = 300, + ) -> TranscriptResult_Ready: + """ + Read the Premium transcript for a video, preparing it first when the account does not own it. + + Reads `quality="premium"`. A ready transcript is returned as is. When preparation is required, + this posts `{video_id}` once, polls the job at the pace the API asks for, and returns the + transcript when it is ready. The default spends included credits only and moves no money. + A positive `max_on_demand_cents` authorizes that many cents of on-demand spend, and the call + sends an Idempotency-Key and the quoted `max_rows`. + + Raises `PreparationTimeoutError` (carrying the job) when `timeout_seconds` pass first, + `PreparationFailedError` when the job fails or is refunded, and `PremiumUnavailableError` + when the plan has no Premium. API refusals raise `ApiError`. + """ + plan = _plan(video_id, max_on_demand_cents, timeout_seconds) + raw = self.with_raw_response + try: + step = next(plan) + while True: + kind, argument = step + if kind == _SLEEP: + time.sleep(argument) + reply = None + elif kind == _READ: + reply = raw.get(video_id, quality="premium") + elif kind == _SUBMIT: + reply = raw.request(**argument) + else: + reply = raw.status(argument) + step = plan.send(reply) + except StopIteration as done: + return done.value + + +class AsyncPrepareAndWait: + async def prepare_and_wait( + self: AsyncTranscriptsClient, + video_id: str, + *, + max_on_demand_cents: int = 0, + timeout_seconds: float = 300, + ) -> TranscriptResult_Ready: + """ + Read the Premium transcript for a video, preparing it first when the account does not own it. + + The asynchronous twin of `Arcmira.transcripts.prepare_and_wait`; it takes the same arguments, + follows the same plan and raises the same errors. + """ + plan = _plan(video_id, max_on_demand_cents, timeout_seconds) + raw = self.with_raw_response + try: + step = next(plan) + while True: + kind, argument = step + if kind == _SLEEP: + await asyncio.sleep(argument) + reply = None + elif kind == _READ: + reply = await raw.get(video_id, quality="premium") + elif kind == _SUBMIT: + reply = await raw.request(**argument) + else: + reply = await raw.status(argument) + step = plan.send(reply) + except StopIteration as done: + return done.value diff --git a/src/arcmira/__init__.py b/src/arcmira/__init__.py index 2215184..9fd5c1e 100644 --- a/src/arcmira/__init__.py +++ b/src/arcmira/__init__.py @@ -1433,3 +1433,4 @@ def __dir__(): ] from ._package import __version__, homepage, docs, api_base, openapi, llms_txt, docs_llms_txt +from .transcripts.prepare import PreparationError, PreparationFailedError, PreparationTimeoutError, PremiumUnavailableError diff --git a/src/arcmira/transcripts/client.py b/src/arcmira/transcripts/client.py index 855f49e..74d9751 100644 --- a/src/arcmira/transcripts/client.py +++ b/src/arcmira/transcripts/client.py @@ -15,6 +15,7 @@ from ..types.transcript_result import TranscriptResult from ..types.transcript_search_response import TranscriptSearchResponse from ..types.video_captions_response import VideoCaptionsResponse +from .prepare import AsyncPrepareAndWait, PrepareAndWait from .raw_client import AsyncRawTranscriptsClient, RawTranscriptsClient from .types.get_transcripts_request_quality import GetTranscriptsRequestQuality from .types.search_transcripts_request_source import SearchTranscriptsRequestSource @@ -27,7 +28,7 @@ OMIT = typing.cast(typing.Any, ...) -class TranscriptsClient: +class TranscriptsClient(PrepareAndWait): def __init__(self, *, client_wrapper: SyncClientWrapper): self._raw_client = RawTranscriptsClient(client_wrapper=client_wrapper) self._client_wrapper = client_wrapper @@ -433,7 +434,7 @@ def merges(self): return self._merges -class AsyncTranscriptsClient: +class AsyncTranscriptsClient(AsyncPrepareAndWait): def __init__(self, *, client_wrapper: AsyncClientWrapper): self._raw_client = AsyncRawTranscriptsClient(client_wrapper=client_wrapper) self._client_wrapper = client_wrapper diff --git a/src/arcmira/transcripts/prepare.py b/src/arcmira/transcripts/prepare.py new file mode 100644 index 0000000..2d0770a --- /dev/null +++ b/src/arcmira/transcripts/prepare.py @@ -0,0 +1,179 @@ +# Maintained by hand. scripts/install-generated.py copies this to +# src/arcmira/transcripts/prepare.py and makes the generated transcripts +# clients inherit these mixins, so regeneration keeps prepare_and_wait. + +from __future__ import annotations + +import asyncio +import time +import typing +import uuid + +from ..types.transcript_job import TranscriptJob +from ..types.transcript_result import TranscriptResult_Ready + +if typing.TYPE_CHECKING: + from .client import AsyncTranscriptsClient, TranscriptsClient + +DEFAULT_POLL_SECONDS = 5 + + +class PreparationError(Exception): + """Base class for prepare_and_wait refusals that are not HTTP errors.""" + + +class PreparationTimeoutError(PreparationError): + """The Premium job was still pending when timeout_seconds ran out. + + The job keeps running. Read the transcript later, or poll `job.status_url`. + """ + + def __init__(self, job: TranscriptJob) -> None: + super().__init__(f"Premium job {job.id} for {job.video_id} is still {job.status}") + self.job = job + + +class PreparationFailedError(PreparationError): + """The Premium job ended failed or refunded, or finished without a servable transcript.""" + + def __init__(self, job: TranscriptJob) -> None: + super().__init__(f"Premium job {job.id} for {job.video_id} ended {job.state}: {job.error or job.status}") + self.job = job + + +class PremiumUnavailableError(PreparationError): + """The account was served captions instead of Premium, so nothing can be prepared.""" + + def __init__(self, transcript: TranscriptResult_Ready) -> None: + super().__init__(f"Premium is not available for this account; the read returned {transcript.quality}") + self.transcript = transcript + + +# One step of the plan. The sync and async clients run the same plan, so +# they cannot drift on when to submit, when to poll, or when to give up. +_READ, _SUBMIT, _STATUS, _SLEEP = "read", "submit", "status", "sleep" +_Step = typing.Tuple[str, typing.Any] + + +def _delay(response: typing.Any, job: TranscriptJob) -> float: + header = response.response.headers.get("retry-after") + if header is not None and header.strip().isdigit(): + return float(header) + return float(job.next_poll_seconds if job.next_poll_seconds is not None else DEFAULT_POLL_SECONDS) + + +def _owned(transcript: typing.Any) -> TranscriptResult_Ready: + if transcript.quality != "premium": + raise PremiumUnavailableError(transcript) + return transcript + + +def _plan( + video_id: str, max_on_demand_cents: int, timeout_seconds: float +) -> typing.Generator[_Step, typing.Any, TranscriptResult_Ready]: + if max_on_demand_cents < 0: + raise ValueError("max_on_demand_cents cannot be negative") + deadline = time.monotonic() + timeout_seconds + read = yield (_READ, None) + transcript = read.data + if transcript.state == "ready": + return _owned(transcript) + if transcript.state == "pending": + response, job = read, transcript.job + else: + submission: typing.Dict[str, typing.Any] = {"video_id": video_id} + if max_on_demand_cents > 0: + # Spending money is the only case that needs a key; one key serves this call and its own retries. + submission.update(max_on_demand_cents=max_on_demand_cents, idempotency_key=str(uuid.uuid4())) + if transcript.quote is not None: + submission["max_rows"] = transcript.quote.rows + response = yield (_SUBMIT, submission) + job = response.data.job + while job.state == "pending": + remaining = deadline - time.monotonic() + if remaining <= 0: + raise PreparationTimeoutError(job) + yield (_SLEEP, min(_delay(response, job), remaining)) + response = yield (_STATUS, job.id) + job = response.data + if job.state != "ready": + raise PreparationFailedError(job) + final = (yield (_READ, None)).data + if final.state != "ready": + raise PreparationFailedError(job) + return _owned(final) + + +class PrepareAndWait: + def prepare_and_wait( + self: TranscriptsClient, + video_id: str, + *, + max_on_demand_cents: int = 0, + timeout_seconds: float = 300, + ) -> TranscriptResult_Ready: + """ + Read the Premium transcript for a video, preparing it first when the account does not own it. + + Reads `quality="premium"`. A ready transcript is returned as is. When preparation is required, + this posts `{video_id}` once, polls the job at the pace the API asks for, and returns the + transcript when it is ready. The default spends included credits only and moves no money. + A positive `max_on_demand_cents` authorizes that many cents of on-demand spend, and the call + sends an Idempotency-Key and the quoted `max_rows`. + + Raises `PreparationTimeoutError` (carrying the job) when `timeout_seconds` pass first, + `PreparationFailedError` when the job fails or is refunded, and `PremiumUnavailableError` + when the plan has no Premium. API refusals raise `ApiError`. + """ + plan = _plan(video_id, max_on_demand_cents, timeout_seconds) + raw = self.with_raw_response + try: + step = next(plan) + while True: + kind, argument = step + if kind == _SLEEP: + time.sleep(argument) + reply = None + elif kind == _READ: + reply = raw.get(video_id, quality="premium") + elif kind == _SUBMIT: + reply = raw.request(**argument) + else: + reply = raw.status(argument) + step = plan.send(reply) + except StopIteration as done: + return done.value + + +class AsyncPrepareAndWait: + async def prepare_and_wait( + self: AsyncTranscriptsClient, + video_id: str, + *, + max_on_demand_cents: int = 0, + timeout_seconds: float = 300, + ) -> TranscriptResult_Ready: + """ + Read the Premium transcript for a video, preparing it first when the account does not own it. + + The asynchronous twin of `Arcmira.transcripts.prepare_and_wait`; it takes the same arguments, + follows the same plan and raises the same errors. + """ + plan = _plan(video_id, max_on_demand_cents, timeout_seconds) + raw = self.with_raw_response + try: + step = next(plan) + while True: + kind, argument = step + if kind == _SLEEP: + await asyncio.sleep(argument) + reply = None + elif kind == _READ: + reply = await raw.get(video_id, quality="premium") + elif kind == _SUBMIT: + reply = await raw.request(**argument) + else: + reply = await raw.status(argument) + step = plan.send(reply) + except StopIteration as done: + return done.value From 10e06d3c29c5fc19eec592941134dbffb5f016c4 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Fri, 2 Oct 2026 00:44:20 -0700 Subject: [PATCH 10/12] Test prepare_and_wait against the bodies the backend sends --- tests/test_generation.py | 5 + tests/test_prepare.py | 225 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 tests/test_prepare.py diff --git a/tests/test_generation.py b/tests/test_generation.py index 4df806f..d65ea91 100644 --- a/tests/test_generation.py +++ b/tests/test_generation.py @@ -34,5 +34,10 @@ def test_installed_api_error_matches_the_preserved_override(self): self.assertEqual((ROOT / 'src/arcmira/core/api_error.py').read_text(), (ROOT / 'scripts/overrides/api_error.py').read_text()) self.assertIn('overrides/api_error.py', (ROOT / 'scripts/install-generated.py').read_text()) + def test_installed_prepare_and_wait_matches_the_preserved_override(self): + self.assertEqual((ROOT / 'src/arcmira/transcripts/prepare.py').read_text(), (ROOT / 'scripts/overrides/prepare.py').read_text()) + client = (ROOT / 'src/arcmira/transcripts/client.py').read_text() + self.assertIn('class TranscriptsClient(PrepareAndWait):', client) + self.assertIn('class AsyncTranscriptsClient(AsyncPrepareAndWait):', client) if __name__ == '__main__': unittest.main() diff --git a/tests/test_prepare.py b/tests/test_prepare.py new file mode 100644 index 0000000..72853d3 --- /dev/null +++ b/tests/test_prepare.py @@ -0,0 +1,225 @@ +import asyncio +import json +import threading +import types +import unittest +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from unittest import mock +from urllib.parse import parse_qs, urlparse + +from arcmira import ( + Arcmira, + AsyncArcmira, + PreparationFailedError, + PreparationTimeoutError, + PremiumUnavailableError, +) +from arcmira.core.api_error import ApiError +from arcmira.transcripts import prepare +from arcmira.types.transcript_result import TranscriptResult_Ready + +FIXTURES = json.loads((Path(__file__).parent / 'fixtures/transcription-responses.json').read_text()) +body = lambda name: FIXTURES[name]['body'] +VIDEO = 'dQw4w9WgXcQ' +JOB = body('get_transcription')['id'] +READ = f'/v1/transcripts/{VIDEO}' +SUBMIT = '/v1/transcriptions' +POLL = f'/v1/transcriptions/{JOB}' +PRICE_REFUSED = {'error': {'type': 'permission_error', 'code': 'spend_limit_exceeded', 'message': 'The spend limit would be exceeded.', 'doc_url': 'https://arcmira.com/docs/errors', 'request_id': 'fixture-request'}} + + +def reply(name, status=200, retry_after=None): + return status, retry_after, body(name) + + +class Handler(BaseHTTPRequestHandler): + script = {} + calls = [] + + def log_message(self, *args): + pass + + def do_GET(self): + self.answer() + + def do_POST(self): + self.answer() + + def answer(self): + path = urlparse(self.path).path + payload = self.rfile.read(int(self.headers.get('Content-Length', 0))).decode() + Handler.calls.append((self.command, path, parse_qs(urlparse(self.path).query), dict(self.headers), payload)) + queue = Handler.script[(self.command, path)] + status, retry_after, result = queue.pop(0) if len(queue) > 1 else queue[0] + self.send_response(status) + self.send_header('Content-Type', 'application/json') + if retry_after is not None: + self.send_header('Retry-After', str(retry_after)) + self.end_headers() + self.wfile.write(json.dumps(result).encode()) + + +class FakeClock: + def __init__(self): + self.now = 0.0 + self.slept = [] + + def monotonic(self): + return self.now + + def sleep(self, seconds): + self.slept.append(seconds) + self.now += seconds + + +class PrepareAndWaitTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) + cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True) + cls.thread.start() + cls.base = f'http://127.0.0.1:{cls.server.server_port}' + cls.client = Arcmira(api_key='local-test-key', base_url=cls.base, max_retries=0, timeout=5) + + @classmethod + def tearDownClass(cls): + cls.server.shutdown() + cls.server.server_close() + cls.thread.join() + + def setUp(self): + Handler.calls = [] + self.clock = FakeClock() + patches = [ + mock.patch.object(prepare, 'time', self.clock), + mock.patch.object(prepare, 'asyncio', types.SimpleNamespace(sleep=self.async_sleep)), + ] + for patch in patches: + patch.start() + self.addCleanup(patch.stop) + + async def async_sleep(self, seconds): + self.clock.sleep(seconds) + + def script(self, **routes): + Handler.script = { + ('GET', READ): routes['read'], + ('POST', SUBMIT): routes.get('submit', [reply('submit_pending', 202)]), + ('GET', POLL): routes.get('poll', [reply('job_ready')]), + } + + def requests(self, method, path): + return [call for call in Handler.calls if call[0] == method and call[1] == path] + + def assert_zero_dollar_post(self): + (_, _, _, headers, payload), = self.requests('POST', SUBMIT) + self.assertEqual(json.loads(payload), {'video_id': VIDEO}) + self.assertNotIn('Idempotency-Key', headers) + + def test_ready_returns_the_owned_transcript_without_posting(self): + self.script(read=[reply('premium_ready')]) + transcript = self.client.transcripts.prepare_and_wait(VIDEO) + self.assertIsInstance(transcript, TranscriptResult_Ready) + self.assertEqual((transcript.quality, transcript.rows_billed), ('premium', 0)) + self.assertEqual([line.speaker for line in transcript.lines], [0, 1]) + self.assertEqual(self.requests('GET', READ)[0][2], {'quality': ['premium']}) + self.assertEqual(self.requests('POST', SUBMIT), []) + self.assertEqual(self.clock.slept, []) + + def test_preparation_required_posts_once_polls_and_reads_the_transcript(self): + self.script( + read=[reply('preparation_required'), reply('premium_ready')], + poll=[reply('get_transcription', retry_after=3), reply('job_ready')], + ) + transcript = self.client.transcripts.prepare_and_wait(VIDEO) + self.assertEqual(transcript.quality, 'premium') + self.assert_zero_dollar_post() + self.assertEqual(len(self.requests('GET', POLL)), 2) + self.assertEqual(len(self.requests('GET', READ)), 2) + self.assertEqual(self.clock.slept, [29, 3]) + + def test_a_pending_read_polls_the_job_without_posting(self): + self.script( + read=[reply('pending_premium', 202, retry_after=5), reply('premium_ready')], + poll=[reply('job_ready')], + ) + self.assertEqual(self.client.transcripts.prepare_and_wait(VIDEO).quality, 'premium') + self.assertEqual(self.requests('POST', SUBMIT), []) + self.assertEqual(self.clock.slept, [5]) + + def test_a_ready_submission_skips_polling(self): + self.script(read=[reply('preparation_required'), reply('premium_ready')], submit=[reply('submit_ready')]) + self.client.transcripts.prepare_and_wait(VIDEO) + self.assertEqual(self.requests('GET', POLL), []) + self.assertEqual(self.clock.slept, []) + + def test_money_sends_the_quoted_max_rows_and_one_idempotency_key(self): + self.script(read=[reply('preparation_required'), reply('premium_ready')]) + self.client.transcripts.prepare_and_wait(VIDEO, max_on_demand_cents=25) + (_, _, _, headers, payload), = self.requests('POST', SUBMIT) + self.assertEqual(json.loads(payload), {'video_id': VIDEO, 'max_rows': 300, 'max_on_demand_cents': 25}) + self.assertRegex(headers['Idempotency-Key'], r'^[0-9a-f-]{36}$') + + def test_a_pending_job_that_outlasts_the_timeout_raises_with_the_job(self): + self.script(read=[reply('preparation_required')], poll=[reply('get_transcription')]) + with self.assertRaises(PreparationTimeoutError) as caught: + self.client.transcripts.prepare_and_wait(VIDEO, timeout_seconds=40) + self.assertEqual(caught.exception.job.id, JOB) + self.assertEqual(caught.exception.job.state, 'pending') + self.assertEqual(self.clock.slept, [29, 11]) + + def test_a_refunded_job_raises_and_never_reposts(self): + self.script(read=[reply('preparation_required')], poll=[reply('job_refunded')]) + with self.assertRaises(PreparationFailedError) as caught: + self.client.transcripts.prepare_and_wait(VIDEO) + self.assertEqual((caught.exception.job.state, caught.exception.job.error), ('refunded', 'Transcription timed out.')) + self.assertEqual(len(self.requests('POST', SUBMIT)), 1) + + def test_captions_are_never_returned_for_a_premium_ask(self): + self.script(read=[reply('get_transcript')]) + with self.assertRaises(PremiumUnavailableError) as caught: + self.client.transcripts.prepare_and_wait(VIDEO) + self.assertEqual(caught.exception.transcript.quality, 'captions') + + def test_an_api_refusal_surfaces_as_api_error(self): + self.script(read=[reply('preparation_required')], submit=[(402, None, PRICE_REFUSED)]) + with self.assertRaises(ApiError) as caught: + self.client.transcripts.prepare_and_wait(VIDEO, max_on_demand_cents=25) + self.assertEqual(str(caught.exception), '402 spend_limit_exceeded: The spend limit would be exceeded.') + + def test_negative_cents_are_refused_before_any_request(self): + with self.assertRaises(ValueError): + self.client.transcripts.prepare_and_wait(VIDEO, max_on_demand_cents=-1) + self.assertEqual(Handler.calls, []) + + def test_async_client_follows_the_same_plan(self): + async def run(): + client = AsyncArcmira(api_key='local-test-key', base_url=self.base, max_retries=0, timeout=5) + self.script( + read=[reply('preparation_required'), reply('premium_ready')], + poll=[reply('get_transcription', retry_after=3), reply('job_ready')], + ) + transcript = await client.transcripts.prepare_and_wait(VIDEO) + self.assertEqual(transcript.quality, 'premium') + self.assert_zero_dollar_post() + self.assertEqual(self.clock.slept, [29, 3]) + + self.script(read=[reply('preparation_required')], poll=[reply('get_transcription')]) + Handler.calls.clear() + self.clock.slept.clear() + with self.assertRaises(PreparationTimeoutError) as caught: + await client.transcripts.prepare_and_wait(VIDEO, timeout_seconds=40) + self.assertEqual(caught.exception.job.id, JOB) + + self.script(read=[reply('preparation_required'), reply('premium_ready')]) + Handler.calls.clear() + await client.transcripts.prepare_and_wait(VIDEO, max_on_demand_cents=25) + (_, _, _, headers, payload), = self.requests('POST', SUBMIT) + self.assertEqual(json.loads(payload)['max_rows'], 300) + self.assertIn('Idempotency-Key', headers) + asyncio.run(run()) + + +if __name__ == '__main__': + unittest.main() From 5a4d2e4a3efb1ecfe84f62d201113f31535bd9ba Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Fri, 2 Oct 2026 00:45:01 -0700 Subject: [PATCH 11/12] Describe the SDK in llms.txt --- llms.txt | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/llms.txt b/llms.txt index f121f92..3f49e97 100644 --- a/llms.txt +++ b/llms.txt @@ -2,7 +2,40 @@ > Arcmira is an SF-based AI company and the search engine for the spoken web. -This GitHub repo and the `arcmira` PyPI package reserve the public name. They are not an SDK. The live agent index is on the website. Fetch that, not a copy inside this package. +This repo is the `arcmira` Python SDK for the Arcmira API: synchronous and asynchronous clients, typed responses and cursor pagination. The live agent index is on the website. Fetch that for product, API and citation rules, not a copy inside this package. + +## Install + +```sh +pip install arcmira +``` + +Set `ARCMIRA_API_KEY` or pass `api_key` to the client. + +## Premium transcript quickstart + +```python +from arcmira import Arcmira + +transcript = Arcmira().transcripts.prepare_and_wait("dQw4w9WgXcQ") +print(transcript.lines) +``` + +`prepare_and_wait(video_id, *, max_on_demand_cents=0, timeout_seconds=300)` reads the Premium transcript. When the account does not own it, the call posts the purchase once, polls the job at the pace the API sets and returns the transcript. The default spends included credits only. Pass `max_on_demand_cents` only for an amount the user approved. + +## Clients + +- `Arcmira` is synchronous. `AsyncArcmira` has the same methods to await, for example `await client.transcripts.prepare_and_wait(video_id)`. +- `client.transcripts.with_raw_response` returns the status code and headers beside the typed data. +- Every Premium purchase answers one `Job`: `client.transcripts.request(video_id=...)` submits it and `client.transcripts.status(job_id)` polls it. + +## Pagination + +List methods such as `client.transcripts.list_requests(limit=10)` and `client.channels.videos.list(channel_id=..., limit=10)` return iterators that follow `next_cursor` for you. Cursors are opaque. Keep filters the same between pages. Async clients return async iterators after awaiting the first page. + +## Errors + +An API refusal raises a typed error derived from `arcmira.core.api_error.ApiError`. Its `body` holds the public error, quote and recovery URLs. `prepare_and_wait` also raises `PreparationTimeoutError` and `PreparationFailedError`, both carrying `.job`, and `PremiumUnavailableError` when the plan has no Premium. Captions are never returned as Premium. ## Canonical @@ -16,6 +49,6 @@ This GitHub repo and the `arcmira` PyPI package reserve the public name. They ar - [Pricing](https://arcmira.com/pricing) - [Contact](mailto:hi@arcmira.com): hi@arcmira.com -There is no SDK in this package. The MCP server ships from https://github.com/arcmira/mcp. +The MCP server ships from https://github.com/arcmira/mcp. -Copyright Arcmira. All rights reserved. +Apache-2.0. See LICENSE. From df17b5ccdce4901561ed1e693adb544c228d7518 Mon Sep 17 00:00:00 2001 From: Zeal Caiden Date: Fri, 2 Oct 2026 01:14:43 -0700 Subject: [PATCH 12/12] Regenerate from the v1 OpenAPI frozen at 798865d9 Purchase refusals now parse as the shared Error with quote, so the refusal test reads the 402 quota_exceeded body POST answers. --- fern/openapi.json | 423 ++++++++++++++++-- reference.md | 2 +- src/arcmira/__init__.py | 108 +++++ src/arcmira/transcripts/client.py | 4 +- src/arcmira/transcripts/raw_client.py | 4 +- src/arcmira/types/__init__.py | 110 +++++ .../types/channel_sponsors_response_access.py | 2 + .../types/entity_momentum_response_access.py | 2 + src/arcmira/types/error.py | 11 + src/arcmira/types/error_error.py | 2 + src/arcmira/types/error_quote.py | 33 ++ src/arcmira/types/error_quote_charge.py | 36 ++ src/arcmira/types/error_quote_charge_from.py | 5 + src/arcmira/types/error_quote_charge_unit.py | 5 + src/arcmira/types/error_resource.py | 267 +++++++++++ src/arcmira/types/error_resource_chart.py | 17 + .../types/error_resource_commercial.py | 20 + .../types/error_resource_commercial_what.py | 7 + src/arcmira/types/error_resource_counts.py | 17 + src/arcmira/types/error_resource_feature.py | 20 + .../types/error_resource_feature_feature.py | 5 + src/arcmira/types/error_resource_filter.py | 19 + .../types/error_resource_fresh_media.py | 20 + src/arcmira/types/error_resource_key.py | 20 + src/arcmira/types/error_resource_key_scope.py | 7 + .../types/error_resource_media_rows.py | 19 + .../types/error_resource_pagination.py | 20 + .../types/error_resource_pagination_param.py | 5 + .../error_resource_premium_transcript.py | 17 + src/arcmira/types/error_resource_requests.py | 17 + src/arcmira/types/error_resource_rows.py | 20 + .../types/error_resource_sidebar_rows.py | 21 + .../error_resource_sidebar_rows_section.py | 5 + src/arcmira/types/transcript_job.py | 4 +- .../types/transcript_purchase_quote.py | 1 - .../types/transcript_response_access.py | 2 + .../transcript_search_response_access.py | 2 + tests/fixtures/transcription-responses.json | 28 ++ tests/test_transcription.py | 20 +- 39 files changed, 1305 insertions(+), 42 deletions(-) create mode 100644 src/arcmira/types/error_quote.py create mode 100644 src/arcmira/types/error_quote_charge.py create mode 100644 src/arcmira/types/error_quote_charge_from.py create mode 100644 src/arcmira/types/error_quote_charge_unit.py create mode 100644 src/arcmira/types/error_resource.py create mode 100644 src/arcmira/types/error_resource_chart.py create mode 100644 src/arcmira/types/error_resource_commercial.py create mode 100644 src/arcmira/types/error_resource_commercial_what.py create mode 100644 src/arcmira/types/error_resource_counts.py create mode 100644 src/arcmira/types/error_resource_feature.py create mode 100644 src/arcmira/types/error_resource_feature_feature.py create mode 100644 src/arcmira/types/error_resource_filter.py create mode 100644 src/arcmira/types/error_resource_fresh_media.py create mode 100644 src/arcmira/types/error_resource_key.py create mode 100644 src/arcmira/types/error_resource_key_scope.py create mode 100644 src/arcmira/types/error_resource_media_rows.py create mode 100644 src/arcmira/types/error_resource_pagination.py create mode 100644 src/arcmira/types/error_resource_pagination_param.py create mode 100644 src/arcmira/types/error_resource_premium_transcript.py create mode 100644 src/arcmira/types/error_resource_requests.py create mode 100644 src/arcmira/types/error_resource_rows.py create mode 100644 src/arcmira/types/error_resource_sidebar_rows.py create mode 100644 src/arcmira/types/error_resource_sidebar_rows_section.py diff --git a/fern/openapi.json b/fern/openapi.json index 78a763a..4e09c1c 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -215,6 +215,57 @@ "Error": { "type": "object", "properties": { + "quote": { + "allOf": [ + { + "$ref": "#/components/schemas/TranscriptQuote" + }, + { + "type": "object", + "properties": { + "charge": { + "type": "object", + "properties": { + "unit": { + "type": "string", + "enum": [ + "rows", + "credits" + ] + }, + "amount": { + "type": "number" + }, + "from": { + "type": "string", + "enum": [ + "included", + "on_demand", + "mixed" + ], + "description": "Where the charge would come from at the current balance." + } + }, + "required": [ + "unit", + "amount", + "from" + ], + "description": "What the purchase would charge at the current balance. Absent when no current price could be read." + }, + "max_on_demand_cents": { + "type": "integer", + "description": "The money ceiling the current quote needs, in whole cents. Send at least this as max_on_demand_cents with a new intent." + } + } + } + ], + "description": "The refused price, on a priced refusal: quota_exceeded, max_rows_exceeded, max_charge_exceeded, spend_limit_exceeded, purchase_authority_changed and paid_plan_required." + }, + "existing_request_id": { + "type": "string", + "description": "On max_charge_exceeded: the accepted purchase for this video that holds a higher money ceiling. Poll it at /v1/transcriptions/{id} instead of starting another." + }, "existingId": { "type": "string", "description": "On tracker_already_exists, the existing tracker id. Reuse it instead of creating another tracker." @@ -270,6 +321,9 @@ ], "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." }, + "resource": { + "$ref": "#/components/schemas/ErrorResource" + }, "unlock": { "type": "object", "properties": { @@ -673,6 +727,300 @@ } ] }, + "TranscriptQuote": { + "type": "object", + "properties": { + "quarters": { + "type": "integer", + "description": "Number of 15-minute blocks in the video, ceiling'd, minimum 1." + }, + "rows": { + "type": "integer", + "description": "Total unlock cost in rows: 75 rows per 15-minute block." + } + }, + "required": [ + "quarters", + "rows" + ] + }, + "ErrorResource": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "media_rows" + ] + }, + "beyond_row": { + "type": "integer" + } + }, + "required": [ + "kind", + "beyond_row" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "fresh_media" + ] + }, + "window_days": { + "type": "integer" + }, + "cutoff": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "window_days", + "cutoff" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "sidebar_rows" + ] + }, + "section": { + "type": "string", + "enum": [ + "topics", + "entities" + ] + }, + "beyond_row": { + "type": "integer" + } + }, + "required": [ + "kind", + "section", + "beyond_row" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "counts" + ] + } + }, + "required": [ + "kind" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "chart" + ] + } + }, + "required": [ + "kind" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "pagination" + ] + }, + "param": { + "type": [ + "string", + "null" + ], + "enum": [ + "offset", + "cursor", + null + ] + } + }, + "required": [ + "kind", + "param" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "premium_transcript" + ] + } + }, + "required": [ + "kind" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "filter" + ] + }, + "param": { + "type": "string" + } + }, + "required": [ + "kind", + "param" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "commercial" + ] + }, + "what": { + "type": "string", + "enum": [ + "sponsors", + "recommendations", + "mention_details", + "community_review", + "paid_split" + ] + } + }, + "required": [ + "kind", + "what" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "feature" + ] + }, + "feature": { + "type": "string", + "enum": [ + "api", + "export" + ] + } + }, + "required": [ + "kind", + "feature" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "rows" + ] + }, + "requested": { + "type": [ + "integer", + "null" + ] + }, + "remaining": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "kind", + "requested", + "remaining" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "key" + ] + }, + "scope": { + "type": [ + "string", + "null" + ], + "enum": [ + "read", + "monitors:write", + "trackers:write", + "recommendations:read", + null + ] + } + }, + "required": [ + "kind", + "scope" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "requests" + ] + } + }, + "required": [ + "kind" + ] + } + ], + "description": "The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds." + }, "HealthResponse": { "type": "object", "properties": { @@ -3020,6 +3368,9 @@ ], "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." }, + "resource": { + "$ref": "#/components/schemas/ErrorResource" + }, "unlock": { "type": "object", "properties": { @@ -3361,6 +3712,9 @@ ], "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." }, + "resource": { + "$ref": "#/components/schemas/ErrorResource" + }, "unlock": { "type": "object", "properties": { @@ -3842,6 +4196,9 @@ ], "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." }, + "resource": { + "$ref": "#/components/schemas/ErrorResource" + }, "unlock": { "type": "object", "properties": { @@ -10894,6 +11251,9 @@ ], "description": "Which boundary refused. Present on every gate error; switch on it without parsing the message." }, + "resource": { + "$ref": "#/components/schemas/ErrorResource" + }, "unlock": { "type": "object", "properties": { @@ -11199,7 +11559,7 @@ "analyzing", null ], - "description": "User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses." + "description": "User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses and refund_pending." }, "charge": { "type": "object", @@ -11232,7 +11592,7 @@ }, "eta_seconds": { "type": "integer", - "description": "Estimated seconds until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight." + "description": "Estimated seconds until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight; absent on refund_pending, which has no completion ETA." }, "next_poll_seconds": { "type": "integer", @@ -11514,9 +11874,6 @@ "on_demand_cents_per_unit": { "type": "number" }, - "prepare_url": { - "type": "string" - }, "refund_policy": { "type": "string" } @@ -11532,27 +11889,9 @@ "credits_per_row", "max_on_demand_cents", "on_demand_cents_per_unit", - "prepare_url", "refund_policy" ] }, - "TranscriptQuote": { - "type": "object", - "properties": { - "quarters": { - "type": "integer", - "description": "Number of 15-minute blocks in the video, ceiling'd, minimum 1." - }, - "rows": { - "type": "integer", - "description": "Total unlock cost in rows: 75 rows per 15-minute block." - } - }, - "required": [ - "quarters", - "rows" - ] - }, "VideoCaptionsResponse": { "type": "object", "properties": { @@ -22172,7 +22511,7 @@ ], "operationId": "submit_transcription", "summary": "Submit a YouTube video for transcription", - "description": "Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it.", + "description": "Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_body with param video_id. A plan without Premium answers 403 paid_plan_required with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it.", "security": [ { "bearerAuth": [] @@ -22306,16 +22645,46 @@ "$ref": "#/components/responses/AuthenticationError" }, "402": { - "$ref": "#/components/responses/QuotaExceeded" + "description": "quota_exceeded or spend_limit_exceeded. Nothing was charged; quote carries the refused price.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "403": { - "$ref": "#/components/responses/PermissionError" + "description": "paid_plan_required. error.unlock names the plan that buys Premium; quote carries the price.", + "headers": { + "X-Request-Id": { + "$ref": "#/components/headers/X-Request-Id" + }, + "X-Arcmira-Version": { + "$ref": "#/components/headers/X-Arcmira-Version" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { - "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent.", + "description": "Conflict. idempotency_conflict means the Idempotency-Key was finalized with a different request intent. max_rows_exceeded, max_charge_exceeded and purchase_authority_changed carry quote; max_charge_exceeded on an already accepted purchase carries existing_request_id.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" diff --git a/reference.md b/reference.md index ac43cf9..bd26f50 100644 --- a/reference.md +++ b/reference.md @@ -1886,7 +1886,7 @@ client.transcripts.list_requests()
-Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. +Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_body with param video_id. A plan without Premium answers 403 paid_plan_required with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it.
diff --git a/src/arcmira/__init__.py b/src/arcmira/__init__.py index 9fd5c1e..5ad0435 100644 --- a/src/arcmira/__init__.py +++ b/src/arcmira/__init__.py @@ -123,6 +123,42 @@ ErrorErrorType, ErrorErrorUnlock, ErrorErrorUnlockAction, + ErrorQuote, + ErrorQuoteCharge, + ErrorQuoteChargeFrom, + ErrorQuoteChargeUnit, + ErrorResource, + ErrorResourceChart, + ErrorResourceCommercial, + ErrorResourceCommercialWhat, + ErrorResourceCounts, + ErrorResourceFeature, + ErrorResourceFeatureFeature, + ErrorResourceFilter, + ErrorResourceFreshMedia, + ErrorResourceKey, + ErrorResourceKeyScope, + ErrorResourceMediaRows, + ErrorResourcePagination, + ErrorResourcePaginationParam, + ErrorResourcePremiumTranscript, + ErrorResourceRequests, + ErrorResourceRows, + ErrorResourceSidebarRows, + ErrorResourceSidebarRowsSection, + ErrorResource_Chart, + ErrorResource_Commercial, + ErrorResource_Counts, + ErrorResource_Feature, + ErrorResource_Filter, + ErrorResource_FreshMedia, + ErrorResource_Key, + ErrorResource_MediaRows, + ErrorResource_Pagination, + ErrorResource_PremiumTranscript, + ErrorResource_Requests, + ErrorResource_Rows, + ErrorResource_SidebarRows, ExposureMeta, ExposureMetaAccess, ExposureMetaAccessChart, @@ -602,6 +638,42 @@ "ErrorErrorType": ".types", "ErrorErrorUnlock": ".types", "ErrorErrorUnlockAction": ".types", + "ErrorQuote": ".types", + "ErrorQuoteCharge": ".types", + "ErrorQuoteChargeFrom": ".types", + "ErrorQuoteChargeUnit": ".types", + "ErrorResource": ".types", + "ErrorResourceChart": ".types", + "ErrorResourceCommercial": ".types", + "ErrorResourceCommercialWhat": ".types", + "ErrorResourceCounts": ".types", + "ErrorResourceFeature": ".types", + "ErrorResourceFeatureFeature": ".types", + "ErrorResourceFilter": ".types", + "ErrorResourceFreshMedia": ".types", + "ErrorResourceKey": ".types", + "ErrorResourceKeyScope": ".types", + "ErrorResourceMediaRows": ".types", + "ErrorResourcePagination": ".types", + "ErrorResourcePaginationParam": ".types", + "ErrorResourcePremiumTranscript": ".types", + "ErrorResourceRequests": ".types", + "ErrorResourceRows": ".types", + "ErrorResourceSidebarRows": ".types", + "ErrorResourceSidebarRowsSection": ".types", + "ErrorResource_Chart": ".types", + "ErrorResource_Commercial": ".types", + "ErrorResource_Counts": ".types", + "ErrorResource_Feature": ".types", + "ErrorResource_Filter": ".types", + "ErrorResource_FreshMedia": ".types", + "ErrorResource_Key": ".types", + "ErrorResource_MediaRows": ".types", + "ErrorResource_Pagination": ".types", + "ErrorResource_PremiumTranscript": ".types", + "ErrorResource_Requests": ".types", + "ErrorResource_Rows": ".types", + "ErrorResource_SidebarRows": ".types", "ExposureMeta": ".types", "ExposureMetaAccess": ".types", "ExposureMetaAccessChart": ".types", @@ -1092,6 +1164,42 @@ def __dir__(): "ErrorErrorType", "ErrorErrorUnlock", "ErrorErrorUnlockAction", + "ErrorQuote", + "ErrorQuoteCharge", + "ErrorQuoteChargeFrom", + "ErrorQuoteChargeUnit", + "ErrorResource", + "ErrorResourceChart", + "ErrorResourceCommercial", + "ErrorResourceCommercialWhat", + "ErrorResourceCounts", + "ErrorResourceFeature", + "ErrorResourceFeatureFeature", + "ErrorResourceFilter", + "ErrorResourceFreshMedia", + "ErrorResourceKey", + "ErrorResourceKeyScope", + "ErrorResourceMediaRows", + "ErrorResourcePagination", + "ErrorResourcePaginationParam", + "ErrorResourcePremiumTranscript", + "ErrorResourceRequests", + "ErrorResourceRows", + "ErrorResourceSidebarRows", + "ErrorResourceSidebarRowsSection", + "ErrorResource_Chart", + "ErrorResource_Commercial", + "ErrorResource_Counts", + "ErrorResource_Feature", + "ErrorResource_Filter", + "ErrorResource_FreshMedia", + "ErrorResource_Key", + "ErrorResource_MediaRows", + "ErrorResource_Pagination", + "ErrorResource_PremiumTranscript", + "ErrorResource_Requests", + "ErrorResource_Rows", + "ErrorResource_SidebarRows", "ExposureMeta", "ExposureMetaAccess", "ExposureMetaAccessChart", diff --git a/src/arcmira/transcripts/client.py b/src/arcmira/transcripts/client.py index 74d9751..17d8e9c 100644 --- a/src/arcmira/transcripts/client.py +++ b/src/arcmira/transcripts/client.py @@ -330,7 +330,7 @@ def request( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptRequestSubmitResponse: """ - Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_body with param video_id. A plan without Premium answers 403 paid_plan_required with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- @@ -777,7 +777,7 @@ async def request( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptRequestSubmitResponse: """ - Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_body with param video_id. A plan without Premium answers 403 paid_plan_required with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- diff --git a/src/arcmira/transcripts/raw_client.py b/src/arcmira/transcripts/raw_client.py index 190e0e3..f6cd246 100644 --- a/src/arcmira/transcripts/raw_client.py +++ b/src/arcmira/transcripts/raw_client.py @@ -768,7 +768,7 @@ def request( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptRequestSubmitResponse]: """ - Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_body with param video_id. A plan without Premium answers 403 paid_plan_required with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- @@ -1762,7 +1762,7 @@ async def request( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptRequestSubmitResponse]: """ - Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_query with param video_id. A plan without Premium answers 403 forbidden with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. + Explicit whole-video Premium purchase in one request: POST { video_id }. With no max_rows the purchase is capped at the current quote, and max_on_demand_cents defaults to zero, so it spends included rows or credits only and moves no money. Idempotency-Key is optional: without one, a purchase already open or owned for this video is returned with existing: true, and two simultaneous requests buy once. max_on_demand_cents above 0 is the only way to move money and requires both Idempotency-Key and max_rows (400 invalid_body names the missing one in param). A video with no known duration or longer than 12 hours answers 400 invalid_body with param video_id. A plan without Premium answers 403 paid_plan_required with unlock. Accepted price, mode, and debit identity persist across retries. Included rows or credits are reserved up front; monetary on-demand usage is reserved until Premium is ready. Existing owned unlocks cost zero. A terminal generation failure refunds the exact original debit and period before reporting refunded. A repeated key returns the same request; different intent with that key returns idempotency_conflict. The body is { job, existing }. Poll job.status_url after Retry-After. A pending job returns 202; a ready, failed or refunded job returns 200. Idempotency-Replayed: true marks a replay of the same key; a different key joined onto the active purchase answers existing: true without it. Parameters ---------- diff --git a/src/arcmira/types/__init__.py b/src/arcmira/types/__init__.py index 30939a7..a049749 100644 --- a/src/arcmira/types/__init__.py +++ b/src/arcmira/types/__init__.py @@ -122,6 +122,44 @@ from .error_error_type import ErrorErrorType from .error_error_unlock import ErrorErrorUnlock from .error_error_unlock_action import ErrorErrorUnlockAction + from .error_quote import ErrorQuote + from .error_quote_charge import ErrorQuoteCharge + from .error_quote_charge_from import ErrorQuoteChargeFrom + from .error_quote_charge_unit import ErrorQuoteChargeUnit + from .error_resource import ( + ErrorResource, + ErrorResource_Chart, + ErrorResource_Commercial, + ErrorResource_Counts, + ErrorResource_Feature, + ErrorResource_Filter, + ErrorResource_FreshMedia, + ErrorResource_Key, + ErrorResource_MediaRows, + ErrorResource_Pagination, + ErrorResource_PremiumTranscript, + ErrorResource_Requests, + ErrorResource_Rows, + ErrorResource_SidebarRows, + ) + from .error_resource_chart import ErrorResourceChart + from .error_resource_commercial import ErrorResourceCommercial + from .error_resource_commercial_what import ErrorResourceCommercialWhat + from .error_resource_counts import ErrorResourceCounts + from .error_resource_feature import ErrorResourceFeature + from .error_resource_feature_feature import ErrorResourceFeatureFeature + from .error_resource_filter import ErrorResourceFilter + from .error_resource_fresh_media import ErrorResourceFreshMedia + from .error_resource_key import ErrorResourceKey + from .error_resource_key_scope import ErrorResourceKeyScope + from .error_resource_media_rows import ErrorResourceMediaRows + from .error_resource_pagination import ErrorResourcePagination + from .error_resource_pagination_param import ErrorResourcePaginationParam + from .error_resource_premium_transcript import ErrorResourcePremiumTranscript + from .error_resource_requests import ErrorResourceRequests + from .error_resource_rows import ErrorResourceRows + from .error_resource_sidebar_rows import ErrorResourceSidebarRows + from .error_resource_sidebar_rows_section import ErrorResourceSidebarRowsSection from .exposure_meta import ExposureMeta from .exposure_meta_access import ExposureMetaAccess from .exposure_meta_access_chart import ExposureMetaAccessChart @@ -544,6 +582,42 @@ "ErrorErrorType": ".error_error_type", "ErrorErrorUnlock": ".error_error_unlock", "ErrorErrorUnlockAction": ".error_error_unlock_action", + "ErrorQuote": ".error_quote", + "ErrorQuoteCharge": ".error_quote_charge", + "ErrorQuoteChargeFrom": ".error_quote_charge_from", + "ErrorQuoteChargeUnit": ".error_quote_charge_unit", + "ErrorResource": ".error_resource", + "ErrorResourceChart": ".error_resource_chart", + "ErrorResourceCommercial": ".error_resource_commercial", + "ErrorResourceCommercialWhat": ".error_resource_commercial_what", + "ErrorResourceCounts": ".error_resource_counts", + "ErrorResourceFeature": ".error_resource_feature", + "ErrorResourceFeatureFeature": ".error_resource_feature_feature", + "ErrorResourceFilter": ".error_resource_filter", + "ErrorResourceFreshMedia": ".error_resource_fresh_media", + "ErrorResourceKey": ".error_resource_key", + "ErrorResourceKeyScope": ".error_resource_key_scope", + "ErrorResourceMediaRows": ".error_resource_media_rows", + "ErrorResourcePagination": ".error_resource_pagination", + "ErrorResourcePaginationParam": ".error_resource_pagination_param", + "ErrorResourcePremiumTranscript": ".error_resource_premium_transcript", + "ErrorResourceRequests": ".error_resource_requests", + "ErrorResourceRows": ".error_resource_rows", + "ErrorResourceSidebarRows": ".error_resource_sidebar_rows", + "ErrorResourceSidebarRowsSection": ".error_resource_sidebar_rows_section", + "ErrorResource_Chart": ".error_resource", + "ErrorResource_Commercial": ".error_resource", + "ErrorResource_Counts": ".error_resource", + "ErrorResource_Feature": ".error_resource", + "ErrorResource_Filter": ".error_resource", + "ErrorResource_FreshMedia": ".error_resource", + "ErrorResource_Key": ".error_resource", + "ErrorResource_MediaRows": ".error_resource", + "ErrorResource_Pagination": ".error_resource", + "ErrorResource_PremiumTranscript": ".error_resource", + "ErrorResource_Requests": ".error_resource", + "ErrorResource_Rows": ".error_resource", + "ErrorResource_SidebarRows": ".error_resource", "ExposureMeta": ".exposure_meta", "ExposureMetaAccess": ".exposure_meta_access", "ExposureMetaAccessChart": ".exposure_meta_access_chart", @@ -976,6 +1050,42 @@ def __dir__(): "ErrorErrorType", "ErrorErrorUnlock", "ErrorErrorUnlockAction", + "ErrorQuote", + "ErrorQuoteCharge", + "ErrorQuoteChargeFrom", + "ErrorQuoteChargeUnit", + "ErrorResource", + "ErrorResourceChart", + "ErrorResourceCommercial", + "ErrorResourceCommercialWhat", + "ErrorResourceCounts", + "ErrorResourceFeature", + "ErrorResourceFeatureFeature", + "ErrorResourceFilter", + "ErrorResourceFreshMedia", + "ErrorResourceKey", + "ErrorResourceKeyScope", + "ErrorResourceMediaRows", + "ErrorResourcePagination", + "ErrorResourcePaginationParam", + "ErrorResourcePremiumTranscript", + "ErrorResourceRequests", + "ErrorResourceRows", + "ErrorResourceSidebarRows", + "ErrorResourceSidebarRowsSection", + "ErrorResource_Chart", + "ErrorResource_Commercial", + "ErrorResource_Counts", + "ErrorResource_Feature", + "ErrorResource_Filter", + "ErrorResource_FreshMedia", + "ErrorResource_Key", + "ErrorResource_MediaRows", + "ErrorResource_Pagination", + "ErrorResource_PremiumTranscript", + "ErrorResource_Requests", + "ErrorResource_Rows", + "ErrorResource_SidebarRows", "ExposureMeta", "ExposureMetaAccess", "ExposureMetaAccessChart", diff --git a/src/arcmira/types/channel_sponsors_response_access.py b/src/arcmira/types/channel_sponsors_response_access.py index 6adac71..cd6ea7e 100644 --- a/src/arcmira/types/channel_sponsors_response_access.py +++ b/src/arcmira/types/channel_sponsors_response_access.py @@ -8,6 +8,7 @@ from .channel_sponsors_response_access_reason import ChannelSponsorsResponseAccessReason from .channel_sponsors_response_access_type import ChannelSponsorsResponseAccessType from .channel_sponsors_response_access_unlock import ChannelSponsorsResponseAccessUnlock +from .error_resource import ErrorResource class ChannelSponsorsResponseAccess(UniversalBaseModel): @@ -45,6 +46,7 @@ class ChannelSponsorsResponseAccess(UniversalBaseModel): Which boundary refused. Present on every gate error; switch on it without parsing the message. """ + resource: typing.Optional[ErrorResource] = None unlock: typing.Optional[ChannelSponsorsResponseAccessUnlock] = pydantic.Field(default=None) """ How to lift the gate. Present when the gate has an unlock. diff --git a/src/arcmira/types/entity_momentum_response_access.py b/src/arcmira/types/entity_momentum_response_access.py index 71aa044..f3749f3 100644 --- a/src/arcmira/types/entity_momentum_response_access.py +++ b/src/arcmira/types/entity_momentum_response_access.py @@ -8,6 +8,7 @@ from .entity_momentum_response_access_reason import EntityMomentumResponseAccessReason from .entity_momentum_response_access_type import EntityMomentumResponseAccessType from .entity_momentum_response_access_unlock import EntityMomentumResponseAccessUnlock +from .error_resource import ErrorResource class EntityMomentumResponseAccess(UniversalBaseModel): @@ -45,6 +46,7 @@ class EntityMomentumResponseAccess(UniversalBaseModel): Which boundary refused. Present on every gate error; switch on it without parsing the message. """ + resource: typing.Optional[ErrorResource] = None unlock: typing.Optional[EntityMomentumResponseAccessUnlock] = pydantic.Field(default=None) """ How to lift the gate. Present when the gate has an unlock. diff --git a/src/arcmira/types/error.py b/src/arcmira/types/error.py index 686edfc..026ad51 100644 --- a/src/arcmira/types/error.py +++ b/src/arcmira/types/error.py @@ -7,9 +7,20 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata from .error_error import ErrorError +from .error_quote import ErrorQuote class Error(UniversalBaseModel): + quote: typing.Optional[ErrorQuote] = pydantic.Field(default=None) + """ + The refused price, on a priced refusal: quota_exceeded, max_rows_exceeded, max_charge_exceeded, spend_limit_exceeded, purchase_authority_changed and paid_plan_required. + """ + + existing_request_id: typing.Optional[str] = pydantic.Field(default=None) + """ + On max_charge_exceeded: the accepted purchase for this video that holds a higher money ceiling. Poll it at /v1/transcriptions/{id} instead of starting another. + """ + existing_id: typing_extensions.Annotated[ typing.Optional[str], FieldMetadata(alias="existingId"), diff --git a/src/arcmira/types/error_error.py b/src/arcmira/types/error_error.py index 17b6509..f766511 100644 --- a/src/arcmira/types/error_error.py +++ b/src/arcmira/types/error_error.py @@ -8,6 +8,7 @@ from .error_error_reason import ErrorErrorReason from .error_error_type import ErrorErrorType from .error_error_unlock import ErrorErrorUnlock +from .error_resource import ErrorResource class ErrorError(UniversalBaseModel): @@ -41,6 +42,7 @@ class ErrorError(UniversalBaseModel): Which boundary refused. Present on every gate error; switch on it without parsing the message. """ + resource: typing.Optional[ErrorResource] = None unlock: typing.Optional[ErrorErrorUnlock] = pydantic.Field(default=None) """ How to lift the gate. Present when the gate has an unlock. diff --git a/src/arcmira/types/error_quote.py b/src/arcmira/types/error_quote.py new file mode 100644 index 0000000..7b40432 --- /dev/null +++ b/src/arcmira/types/error_quote.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 +from .error_quote_charge import ErrorQuoteCharge +from .transcript_quote import TranscriptQuote + + +class ErrorQuote(TranscriptQuote): + """ + The refused price, on a priced refusal: quota_exceeded, max_rows_exceeded, max_charge_exceeded, spend_limit_exceeded, purchase_authority_changed and paid_plan_required. + """ + + charge: typing.Optional[ErrorQuoteCharge] = pydantic.Field(default=None) + """ + What the purchase would charge at the current balance. Absent when no current price could be read. + """ + + max_on_demand_cents: typing.Optional[int] = pydantic.Field(default=None) + """ + The money ceiling the current quote needs, in whole cents. Send at least this as max_on_demand_cents with a new intent. + """ + + 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/arcmira/types/error_quote_charge.py b/src/arcmira/types/error_quote_charge.py new file mode 100644 index 0000000..42b5433 --- /dev/null +++ b/src/arcmira/types/error_quote_charge.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .error_quote_charge_from import ErrorQuoteChargeFrom +from .error_quote_charge_unit import ErrorQuoteChargeUnit + + +class ErrorQuoteCharge(UniversalBaseModel): + """ + What the purchase would charge at the current balance. Absent when no current price could be read. + """ + + unit: ErrorQuoteChargeUnit + amount: float + from_: typing_extensions.Annotated[ + ErrorQuoteChargeFrom, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Where the charge would come from at the current balance."), + ] + """ + Where the charge would come from at the current balance. + """ + + 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/arcmira/types/error_quote_charge_from.py b/src/arcmira/types/error_quote_charge_from.py new file mode 100644 index 0000000..b8ae461 --- /dev/null +++ b/src/arcmira/types/error_quote_charge_from.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorQuoteChargeFrom = typing.Union[typing.Literal["included", "on_demand", "mixed"], typing.Any] diff --git a/src/arcmira/types/error_quote_charge_unit.py b/src/arcmira/types/error_quote_charge_unit.py new file mode 100644 index 0000000..1368a48 --- /dev/null +++ b/src/arcmira/types/error_quote_charge_unit.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorQuoteChargeUnit = typing.Union[typing.Literal["rows", "credits"], typing.Any] diff --git a/src/arcmira/types/error_resource.py b/src/arcmira/types/error_resource.py new file mode 100644 index 0000000..131ccce --- /dev/null +++ b/src/arcmira/types/error_resource.py @@ -0,0 +1,267 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .error_resource_commercial_what import ErrorResourceCommercialWhat +from .error_resource_feature_feature import ErrorResourceFeatureFeature +from .error_resource_key_scope import ErrorResourceKeyScope +from .error_resource_pagination_param import ErrorResourcePaginationParam +from .error_resource_sidebar_rows_section import ErrorResourceSidebarRowsSection + + +class ErrorResource_MediaRows(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["media_rows"] = "media_rows" + beyond_row: int + + 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 + + +class ErrorResource_FreshMedia(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["fresh_media"] = "fresh_media" + window_days: int + cutoff: 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 + + +class ErrorResource_SidebarRows(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["sidebar_rows"] = "sidebar_rows" + section: ErrorResourceSidebarRowsSection + beyond_row: int + + 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 + + +class ErrorResource_Counts(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["counts"] = "counts" + + 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 + + +class ErrorResource_Chart(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["chart"] = "chart" + + 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 + + +class ErrorResource_Pagination(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["pagination"] = "pagination" + param: typing.Optional[ErrorResourcePaginationParam] = 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 + + +class ErrorResource_PremiumTranscript(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["premium_transcript"] = "premium_transcript" + + 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 + + +class ErrorResource_Filter(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["filter"] = "filter" + param: str + + 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 + + +class ErrorResource_Commercial(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["commercial"] = "commercial" + what: ErrorResourceCommercialWhat + + 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 + + +class ErrorResource_Feature(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["feature"] = "feature" + feature: ErrorResourceFeatureFeature + + 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 + + +class ErrorResource_Rows(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["rows"] = "rows" + requested: typing.Optional[int] = None + remaining: typing.Optional[int] = 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 + + +class ErrorResource_Key(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["key"] = "key" + scope: typing.Optional[ErrorResourceKeyScope] = 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 + + +class ErrorResource_Requests(UniversalBaseModel): + """ + The value the boundary withheld, not the reason it refused. kind is a closed vocabulary and the fields beside it are fixed per kind; see https://arcmira.com/docs/errors#resource-kinds. + """ + + kind: typing.Literal["requests"] = "requests" + + 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 + + +ErrorResource = typing_extensions.Annotated[ + typing.Union[ + ErrorResource_MediaRows, + ErrorResource_FreshMedia, + ErrorResource_SidebarRows, + ErrorResource_Counts, + ErrorResource_Chart, + ErrorResource_Pagination, + ErrorResource_PremiumTranscript, + ErrorResource_Filter, + ErrorResource_Commercial, + ErrorResource_Feature, + ErrorResource_Rows, + ErrorResource_Key, + ErrorResource_Requests, + ], + pydantic.Field(discriminator="kind"), +] diff --git a/src/arcmira/types/error_resource_chart.py b/src/arcmira/types/error_resource_chart.py new file mode 100644 index 0000000..810b1df --- /dev/null +++ b/src/arcmira/types/error_resource_chart.py @@ -0,0 +1,17 @@ +# 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 ErrorResourceChart(UniversalBaseModel): + 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/arcmira/types/error_resource_commercial.py b/src/arcmira/types/error_resource_commercial.py new file mode 100644 index 0000000..66bafb4 --- /dev/null +++ b/src/arcmira/types/error_resource_commercial.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 +from .error_resource_commercial_what import ErrorResourceCommercialWhat + + +class ErrorResourceCommercial(UniversalBaseModel): + what: ErrorResourceCommercialWhat + + 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/arcmira/types/error_resource_commercial_what.py b/src/arcmira/types/error_resource_commercial_what.py new file mode 100644 index 0000000..425f267 --- /dev/null +++ b/src/arcmira/types/error_resource_commercial_what.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorResourceCommercialWhat = typing.Union[ + typing.Literal["sponsors", "recommendations", "mention_details", "community_review", "paid_split"], typing.Any +] diff --git a/src/arcmira/types/error_resource_counts.py b/src/arcmira/types/error_resource_counts.py new file mode 100644 index 0000000..b536456 --- /dev/null +++ b/src/arcmira/types/error_resource_counts.py @@ -0,0 +1,17 @@ +# 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 ErrorResourceCounts(UniversalBaseModel): + 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/arcmira/types/error_resource_feature.py b/src/arcmira/types/error_resource_feature.py new file mode 100644 index 0000000..2f6d992 --- /dev/null +++ b/src/arcmira/types/error_resource_feature.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 +from .error_resource_feature_feature import ErrorResourceFeatureFeature + + +class ErrorResourceFeature(UniversalBaseModel): + feature: ErrorResourceFeatureFeature + + 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/arcmira/types/error_resource_feature_feature.py b/src/arcmira/types/error_resource_feature_feature.py new file mode 100644 index 0000000..89e887c --- /dev/null +++ b/src/arcmira/types/error_resource_feature_feature.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorResourceFeatureFeature = typing.Union[typing.Literal["api", "export"], typing.Any] diff --git a/src/arcmira/types/error_resource_filter.py b/src/arcmira/types/error_resource_filter.py new file mode 100644 index 0000000..f5ad2c2 --- /dev/null +++ b/src/arcmira/types/error_resource_filter.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 ErrorResourceFilter(UniversalBaseModel): + param: str + + 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/arcmira/types/error_resource_fresh_media.py b/src/arcmira/types/error_resource_fresh_media.py new file mode 100644 index 0000000..75ade93 --- /dev/null +++ b/src/arcmira/types/error_resource_fresh_media.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 ErrorResourceFreshMedia(UniversalBaseModel): + window_days: int + cutoff: 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/arcmira/types/error_resource_key.py b/src/arcmira/types/error_resource_key.py new file mode 100644 index 0000000..15d229b --- /dev/null +++ b/src/arcmira/types/error_resource_key.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 +from .error_resource_key_scope import ErrorResourceKeyScope + + +class ErrorResourceKey(UniversalBaseModel): + scope: typing.Optional[ErrorResourceKeyScope] = 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/arcmira/types/error_resource_key_scope.py b/src/arcmira/types/error_resource_key_scope.py new file mode 100644 index 0000000..b0db6b3 --- /dev/null +++ b/src/arcmira/types/error_resource_key_scope.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorResourceKeyScope = typing.Union[ + typing.Literal["read", "monitors:write", "trackers:write", "recommendations:read"], typing.Any +] diff --git a/src/arcmira/types/error_resource_media_rows.py b/src/arcmira/types/error_resource_media_rows.py new file mode 100644 index 0000000..c34f5f7 --- /dev/null +++ b/src/arcmira/types/error_resource_media_rows.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 ErrorResourceMediaRows(UniversalBaseModel): + beyond_row: int + + 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/arcmira/types/error_resource_pagination.py b/src/arcmira/types/error_resource_pagination.py new file mode 100644 index 0000000..b9a53bf --- /dev/null +++ b/src/arcmira/types/error_resource_pagination.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 +from .error_resource_pagination_param import ErrorResourcePaginationParam + + +class ErrorResourcePagination(UniversalBaseModel): + param: typing.Optional[ErrorResourcePaginationParam] = 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/arcmira/types/error_resource_pagination_param.py b/src/arcmira/types/error_resource_pagination_param.py new file mode 100644 index 0000000..3446178 --- /dev/null +++ b/src/arcmira/types/error_resource_pagination_param.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorResourcePaginationParam = typing.Union[typing.Literal["offset", "cursor"], typing.Any] diff --git a/src/arcmira/types/error_resource_premium_transcript.py b/src/arcmira/types/error_resource_premium_transcript.py new file mode 100644 index 0000000..5b918c2 --- /dev/null +++ b/src/arcmira/types/error_resource_premium_transcript.py @@ -0,0 +1,17 @@ +# 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 ErrorResourcePremiumTranscript(UniversalBaseModel): + 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/arcmira/types/error_resource_requests.py b/src/arcmira/types/error_resource_requests.py new file mode 100644 index 0000000..dc6766a --- /dev/null +++ b/src/arcmira/types/error_resource_requests.py @@ -0,0 +1,17 @@ +# 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 ErrorResourceRequests(UniversalBaseModel): + 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/arcmira/types/error_resource_rows.py b/src/arcmira/types/error_resource_rows.py new file mode 100644 index 0000000..9576f65 --- /dev/null +++ b/src/arcmira/types/error_resource_rows.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 ErrorResourceRows(UniversalBaseModel): + requested: typing.Optional[int] = None + remaining: typing.Optional[int] = 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/arcmira/types/error_resource_sidebar_rows.py b/src/arcmira/types/error_resource_sidebar_rows.py new file mode 100644 index 0000000..97067e8 --- /dev/null +++ b/src/arcmira/types/error_resource_sidebar_rows.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 +from .error_resource_sidebar_rows_section import ErrorResourceSidebarRowsSection + + +class ErrorResourceSidebarRows(UniversalBaseModel): + section: ErrorResourceSidebarRowsSection + beyond_row: int + + 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/arcmira/types/error_resource_sidebar_rows_section.py b/src/arcmira/types/error_resource_sidebar_rows_section.py new file mode 100644 index 0000000..5ae7714 --- /dev/null +++ b/src/arcmira/types/error_resource_sidebar_rows_section.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ErrorResourceSidebarRowsSection = typing.Union[typing.Literal["topics", "entities"], typing.Any] diff --git a/src/arcmira/types/transcript_job.py b/src/arcmira/types/transcript_job.py index e5ef0ea..8ea6449 100644 --- a/src/arcmira/types/transcript_job.py +++ b/src/arcmira/types/transcript_job.py @@ -37,7 +37,7 @@ class TranscriptJob(UniversalBaseModel): stage: typing.Optional[TranscriptJobStage] = pydantic.Field(default=None) """ - User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses. + User-facing stage: downloading folds into transcribing. Values: queued (waiting to start), transcribing (downloading or transcribing), analyzing (analysis running). Null for terminal statuses and refund_pending. """ charge: typing.Optional[TranscriptJobCharge] = pydantic.Field(default=None) @@ -47,7 +47,7 @@ class TranscriptJob(UniversalBaseModel): eta_seconds: typing.Optional[int] = pydantic.Field(default=None) """ - Estimated seconds until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight. + Estimated seconds until completion, re-derived from live pipeline telemetry on every poll. Only present while the request is in flight; absent on refund_pending, which has no completion ETA. """ next_poll_seconds: typing.Optional[int] = pydantic.Field(default=None) diff --git a/src/arcmira/types/transcript_purchase_quote.py b/src/arcmira/types/transcript_purchase_quote.py index 71a9602..643d6fc 100644 --- a/src/arcmira/types/transcript_purchase_quote.py +++ b/src/arcmira/types/transcript_purchase_quote.py @@ -26,7 +26,6 @@ class TranscriptPurchaseQuote(UniversalBaseModel): credits_per_row: float max_on_demand_cents: float on_demand_cents_per_unit: float - prepare_url: str refund_policy: str if IS_PYDANTIC_V2: diff --git a/src/arcmira/types/transcript_response_access.py b/src/arcmira/types/transcript_response_access.py index 45d7306..6379722 100644 --- a/src/arcmira/types/transcript_response_access.py +++ b/src/arcmira/types/transcript_response_access.py @@ -4,6 +4,7 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .error_resource import ErrorResource from .transcript_response_access_gate import TranscriptResponseAccessGate from .transcript_response_access_reason import TranscriptResponseAccessReason from .transcript_response_access_type import TranscriptResponseAccessType @@ -45,6 +46,7 @@ class TranscriptResponseAccess(UniversalBaseModel): Which boundary refused. Present on every gate error; switch on it without parsing the message. """ + resource: typing.Optional[ErrorResource] = None unlock: typing.Optional[TranscriptResponseAccessUnlock] = pydantic.Field(default=None) """ How to lift the gate. Present when the gate has an unlock. diff --git a/src/arcmira/types/transcript_search_response_access.py b/src/arcmira/types/transcript_search_response_access.py index 71beb20..c6dd086 100644 --- a/src/arcmira/types/transcript_search_response_access.py +++ b/src/arcmira/types/transcript_search_response_access.py @@ -4,6 +4,7 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .error_resource import ErrorResource from .transcript_search_response_access_gate import TranscriptSearchResponseAccessGate from .transcript_search_response_access_reason import TranscriptSearchResponseAccessReason from .transcript_search_response_access_type import TranscriptSearchResponseAccessType @@ -45,6 +46,7 @@ class TranscriptSearchResponseAccess(UniversalBaseModel): Which boundary refused. Present on every gate error; switch on it without parsing the message. """ + resource: typing.Optional[ErrorResource] = None unlock: typing.Optional[TranscriptSearchResponseAccessUnlock] = pydantic.Field(default=None) """ How to lift the gate. Present when the gate has an unlock. diff --git a/tests/fixtures/transcription-responses.json b/tests/fixtures/transcription-responses.json index 7f7b574..d9724c0 100644 --- a/tests/fixtures/transcription-responses.json +++ b/tests/fixtures/transcription-responses.json @@ -405,5 +405,33 @@ "as_of": "2026-08-05T00:00:00.000Z", "note": "Premium transcripts are Arcmira's own. Every Premium transcript is diarized: each line carries a speaker. We identify each speaker from the audio, the video, and internal and community review. We are right most of the time and wrong sometimes, so when a name matters, say it came from Arcmira's speaker identification and cite the line." } + }, + "refused_quota": { + "status": 402, + "body": { + "error": { + "type": "quota_exceeded", + "code": "quota_exceeded", + "message": "This transcript needs 75 rows and the account has 10 left this month. Raise max_on_demand_cents or upgrade.", + "gate": "rows", + "unlock": { + "tier": "Pro", + "url": "https://arcmira.com/pricing?src=api", + "offer": null + }, + "doc_url": "https://arcmira.com/docs/errors#quota_exceeded", + "request_id": "req_fixture" + }, + "quote": { + "quarters": 1, + "rows": 75, + "charge": { + "unit": "credits", + "amount": 300, + "from": "mixed" + }, + "max_on_demand_cents": 104 + } + } } } diff --git a/tests/test_transcription.py b/tests/test_transcription.py index 0232ede..8eec3b7 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -9,11 +9,13 @@ from arcmira import Arcmira, AsyncArcmira from arcmira.core.api_error import ApiError +from arcmira.errors import PaymentRequiredError from arcmira.types.transcript_result import TranscriptResult_Ready, TranscriptResult_Pending FIXTURES = json.loads((Path(__file__).parent / 'fixtures/transcription-responses.json').read_text()) QUOTE = FIXTURES['quote_transcription']['body'] REQUEST = FIXTURES['get_transcription']['body'] +REFUSED = FIXTURES['refused_quota']['body'] def error(code, kind): return dict(type=kind, code=code, message=code, doc_url='https://arcmira.com/docs/errors', request_id='fixture-request') @@ -41,6 +43,8 @@ def answer(self): status, extra = 200, {} if url.path.endswith('/quote'): result = QUOTE + elif url.path == '/v1/transcriptions' and self.command == 'POST' and 'refused0000' in body: + status, result = 402, REFUSED elif url.path == '/v1/transcriptions' and self.command == 'POST': key = self.headers['Idempotency-Key'] replay = key in RECEIPTS @@ -58,8 +62,6 @@ def answer(self): result = dict(channel={'youtube_channel_id':'UC-test','name':'Fixture'}, episodes=[episode], returned=1, has_more='cursor' not in query, next_cursor=None if 'cursor' in query else CURSOR, indexed_through=None, index_age_days=None, as_of=None, note='Fixture') elif url.path.endswith('/pending0000'): status, result = 202, PENDING - elif url.path.endswith('/refused0000'): - status, result = 403, {'error': error('purchase_required', 'permission_error'), 'quote': QUOTE, 'prepare_url': '/v1/transcriptions'} else: result = FIXTURES['get_transcript']['body'] self.send_response(status) @@ -96,11 +98,15 @@ def test_ready_pending_status_and_union(self): def test_quote_and_refusal(self): quote = self.client.transcripts.quote(video_id='dQw4w9WgXcQ') self.assertEqual(quote.quote.rows, QUOTE['quote']['rows']) - with self.assertRaises(ApiError) as caught: - self.client.transcripts.get(video_id='refused0000', quality='premium') - self.assertEqual(caught.exception.status_code, 403) - self.assertEqual(caught.exception.body.quote, QUOTE) - self.assertEqual(str(caught.exception), '403 purchase_required: purchase_required') + with self.assertRaises(PaymentRequiredError) as caught: + self.client.transcripts.request(video_id='refused0000') + refusal = caught.exception.body + self.assertEqual(refusal.error.code, 'quota_exceeded') + self.assertEqual(refusal.error.unlock.tier, REFUSED['error']['unlock']['tier']) + self.assertEqual(refusal.quote.rows, REFUSED['quote']['rows']) + self.assertEqual(refusal.quote.charge.amount, REFUSED['quote']['charge']['amount']) + self.assertEqual(refusal.quote.max_on_demand_cents, REFUSED['quote']['max_on_demand_cents']) + self.assertEqual(str(caught.exception), f"402 quota_exceeded: {REFUSED['error']['message']}") self.assertNotIn('headers', str(caught.exception)) def test_preparation_exact_intent_and_replay(self):