wefunder 0.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +263 -0
- data/lib/wefunder/client.rb +283 -0
- data/lib/wefunder/errors.rb +87 -0
- data/lib/wefunder/oauth.rb +135 -0
- data/lib/wefunder/pagination.rb +52 -0
- data/lib/wefunder/token_manager.rb +80 -0
- data/lib/wefunder/transport.rb +93 -0
- data/lib/wefunder/version.rb +5 -0
- data/lib/wefunder/webhooks.rb +238 -0
- data/lib/wefunder.rb +26 -0
- data/lib/wefunder_generated/api/activity_api.rb +234 -0
- data/lib/wefunder_generated/api/attribution_api.rb +182 -0
- data/lib/wefunder_generated/api/attribution_partners_api.rb +392 -0
- data/lib/wefunder_generated/api/attribution_webhooks_api.rb +504 -0
- data/lib/wefunder_generated/api/campaigns_api.rb +82 -0
- data/lib/wefunder_generated/api/companies_api.rb +101 -0
- data/lib/wefunder_generated/api/explore_api.rb +633 -0
- data/lib/wefunder_generated/api/installations_api.rb +405 -0
- data/lib/wefunder_generated/api/intents_api.rb +301 -0
- data/lib/wefunder_generated/api/investment_delta_api.rb +99 -0
- data/lib/wefunder_generated/api/investments_api.rb +238 -0
- data/lib/wefunder_generated/api/portfolio_api.rb +166 -0
- data/lib/wefunder_generated/api/syndicate_deals_api.rb +381 -0
- data/lib/wefunder_generated/api/syndicate_members_api.rb +976 -0
- data/lib/wefunder_generated/api/syndicate_portfolio_api.rb +178 -0
- data/lib/wefunder_generated/api/syndicate_statistics_api.rb +85 -0
- data/lib/wefunder_generated/api/syndicates_api.rb +226 -0
- data/lib/wefunder_generated/api/users_api.rb +329 -0
- data/lib/wefunder_generated/api/webhook_endpoints_api.rb +539 -0
- data/lib/wefunder_generated/api_client.rb +437 -0
- data/lib/wefunder_generated/api_error.rb +58 -0
- data/lib/wefunder_generated/configuration.rb +392 -0
- data/lib/wefunder_generated/models/anonymized_attribution.rb +298 -0
- data/lib/wefunder_generated/models/anonymized_attribution_attribution.rb +330 -0
- data/lib/wefunder_generated/models/anonymized_attribution_status.rb +260 -0
- data/lib/wefunder_generated/models/attributed_investment_list_envelope.rb +226 -0
- data/lib/wefunder_generated/models/attributed_investment_list_envelope_data_inner.rb +105 -0
- data/lib/wefunder_generated/models/attributed_investment_list_envelope_meta.rb +295 -0
- data/lib/wefunder_generated/models/attribution_campaign_access.rb +241 -0
- data/lib/wefunder_generated/models/attribution_campaign_summary.rb +251 -0
- data/lib/wefunder_generated/models/attribution_campaign_summary_list_envelope.rb +216 -0
- data/lib/wefunder_generated/models/attribution_me.rb +235 -0
- data/lib/wefunder_generated/models/attribution_me_envelope.rb +214 -0
- data/lib/wefunder_generated/models/attribution_partner.rb +275 -0
- data/lib/wefunder_generated/models/attribution_stats.rb +266 -0
- data/lib/wefunder_generated/models/attribution_stats_by_campaign_inner.rb +232 -0
- data/lib/wefunder_generated/models/attribution_stats_by_source_inner.rb +232 -0
- data/lib/wefunder_generated/models/attribution_stats_envelope.rb +214 -0
- data/lib/wefunder_generated/models/attribution_stats_period.rb +223 -0
- data/lib/wefunder_generated/models/attribution_stats_quality_breakdown.rb +236 -0
- data/lib/wefunder_generated/models/attribution_stats_totals.rb +245 -0
- data/lib/wefunder_generated/models/attribution_user.rb +232 -0
- data/lib/wefunder_generated/models/audit_event.rb +234 -0
- data/lib/wefunder_generated/models/audit_event_attributes.rb +391 -0
- data/lib/wefunder_generated/models/audit_event_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/bulk_invite_link_create_input.rb +243 -0
- data/lib/wefunder_generated/models/bulk_invite_link_envelope.rb +237 -0
- data/lib/wefunder_generated/models/bulk_invite_link_envelope_errors_inner.rb +233 -0
- data/lib/wefunder_generated/models/bulk_invite_link_envelope_meta.rb +232 -0
- data/lib/wefunder_generated/models/campaign.rb +232 -0
- data/lib/wefunder_generated/models/campaign_attributes.rb +301 -0
- data/lib/wefunder_generated/models/campaign_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/comment_proposal_envelope.rb +274 -0
- data/lib/wefunder_generated/models/comment_proposal_envelope_draft.rb +260 -0
- data/lib/wefunder_generated/models/comment_proposal_envelope_proposal.rb +286 -0
- data/lib/wefunder_generated/models/comment_proposal_envelope_related_questions_inner.rb +264 -0
- data/lib/wefunder_generated/models/company.rb +234 -0
- data/lib/wefunder_generated/models/company_attributes.rb +318 -0
- data/lib/wefunder_generated/models/company_attributes_location.rb +235 -0
- data/lib/wefunder_generated/models/company_disclosures.rb +268 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes.rb +434 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_business.rb +255 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_capital_structure_inner.rb +255 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_current_position.rb +281 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_filing.rb +256 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_financial_statements.rb +233 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_outstanding_debts.rb +227 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_outstanding_debts_items_inner.rb +255 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_outstanding_notes_inner.rb +284 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_prior_offerings_inner.rb +266 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_ratios.rb +272 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_related_parties.rb +226 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_related_parties_parties_inner.rb +265 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_use_of_funds_inner.rb +226 -0
- data/lib/wefunder_generated/models/company_disclosures_attributes_voting_power_inner.rb +236 -0
- data/lib/wefunder_generated/models/company_disclosures_envelope.rb +214 -0
- data/lib/wefunder_generated/models/company_envelope.rb +214 -0
- data/lib/wefunder_generated/models/company_pitch.rb +268 -0
- data/lib/wefunder_generated/models/company_pitch_attributes.rb +311 -0
- data/lib/wefunder_generated/models/company_pitch_attributes_perks.rb +248 -0
- data/lib/wefunder_generated/models/company_pitch_attributes_story.rb +255 -0
- data/lib/wefunder_generated/models/company_pitch_envelope.rb +214 -0
- data/lib/wefunder_generated/models/company_question.rb +268 -0
- data/lib/wefunder_generated/models/company_question_attributes.rb +329 -0
- data/lib/wefunder_generated/models/company_question_attributes_answers_inner.rb +253 -0
- data/lib/wefunder_generated/models/company_question_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/company_question_list_envelope_meta.rb +301 -0
- data/lib/wefunder_generated/models/company_question_person.rb +247 -0
- data/lib/wefunder_generated/models/company_search_result.rb +268 -0
- data/lib/wefunder_generated/models/company_search_result_attributes.rb +266 -0
- data/lib/wefunder_generated/models/company_search_result_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/company_search_result_list_envelope_meta.rb +223 -0
- data/lib/wefunder_generated/models/company_totals.rb +249 -0
- data/lib/wefunder_generated/models/company_update.rb +269 -0
- data/lib/wefunder_generated/models/company_update_attributes.rb +350 -0
- data/lib/wefunder_generated/models/company_update_attributes_author.rb +215 -0
- data/lib/wefunder_generated/models/company_update_envelope.rb +214 -0
- data/lib/wefunder_generated/models/company_update_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/company_update_list_envelope_meta.rb +243 -0
- data/lib/wefunder_generated/models/connected_app.rb +233 -0
- data/lib/wefunder_generated/models/connected_app_attributes.rb +258 -0
- data/lib/wefunder_generated/models/connected_app_list_envelope.rb +216 -0
- data/lib/wefunder_generated/models/connected_app_revocation.rb +223 -0
- data/lib/wefunder_generated/models/connected_app_revocation_envelope.rb +214 -0
- data/lib/wefunder_generated/models/convertible_note_security.rb +266 -0
- data/lib/wefunder_generated/models/convertible_note_security_terms.rb +266 -0
- data/lib/wefunder_generated/models/create_installation_request.rb +294 -0
- data/lib/wefunder_generated/models/create_installation_token_request.rb +216 -0
- data/lib/wefunder_generated/models/create_intent_request.rb +320 -0
- data/lib/wefunder_generated/models/create_partner_invite_request.rb +287 -0
- data/lib/wefunder_generated/models/create_webhook_endpoint_request.rb +291 -0
- data/lib/wefunder_generated/models/create_webhook_subscription_request.rb +263 -0
- data/lib/wefunder_generated/models/current_raise.rb +288 -0
- data/lib/wefunder_generated/models/deal_investor.rb +234 -0
- data/lib/wefunder_generated/models/deal_investor_attributes.rb +321 -0
- data/lib/wefunder_generated/models/deal_investor_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/deal_investor_list_envelope_meta.rb +252 -0
- data/lib/wefunder_generated/models/debt_security.rb +266 -0
- data/lib/wefunder_generated/models/debt_security_terms.rb +235 -0
- data/lib/wefunder_generated/models/delete_webhook_endpoint200_response.rb +214 -0
- data/lib/wefunder_generated/models/delete_webhook_endpoint200_response_data.rb +232 -0
- data/lib/wefunder_generated/models/disclosure_document.rb +224 -0
- data/lib/wefunder_generated/models/disclosure_person.rb +254 -0
- data/lib/wefunder_generated/models/eligible_target_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/eligible_target_list_envelope_data_inner.rb +295 -0
- data/lib/wefunder_generated/models/equity_security.rb +266 -0
- data/lib/wefunder_generated/models/equity_security_terms.rb +271 -0
- data/lib/wefunder_generated/models/error.rb +214 -0
- data/lib/wefunder_generated/models/error_error.rb +254 -0
- data/lib/wefunder_generated/models/exemption.rb +271 -0
- data/lib/wefunder_generated/models/fiscal_year_financials.rb +296 -0
- data/lib/wefunder_generated/models/follow_state_envelope.rb +214 -0
- data/lib/wefunder_generated/models/follow_state_envelope_data.rb +268 -0
- data/lib/wefunder_generated/models/follow_state_envelope_data_attributes.rb +225 -0
- data/lib/wefunder_generated/models/followed_company.rb +268 -0
- data/lib/wefunder_generated/models/followed_company_attributes.rb +275 -0
- data/lib/wefunder_generated/models/followed_company_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/full_attribution.rb +272 -0
- data/lib/wefunder_generated/models/full_attribution_attribution.rb +326 -0
- data/lib/wefunder_generated/models/full_attribution_investor.rb +242 -0
- data/lib/wefunder_generated/models/full_attribution_status.rb +257 -0
- data/lib/wefunder_generated/models/fund_security.rb +267 -0
- data/lib/wefunder_generated/models/installation.rb +233 -0
- data/lib/wefunder_generated/models/installation_attributes.rb +307 -0
- data/lib/wefunder_generated/models/installation_attributes_target.rb +267 -0
- data/lib/wefunder_generated/models/installation_envelope.rb +223 -0
- data/lib/wefunder_generated/models/installation_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/installation_list_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/installation_token_envelope.rb +232 -0
- data/lib/wefunder_generated/models/installation_token_envelope_token.rb +252 -0
- data/lib/wefunder_generated/models/intent.rb +234 -0
- data/lib/wefunder_generated/models/intent_attributes.rb +375 -0
- data/lib/wefunder_generated/models/intent_attributes_resource_id.rb +106 -0
- data/lib/wefunder_generated/models/intent_envelope.rb +214 -0
- data/lib/wefunder_generated/models/intent_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/intent_preview_envelope.rb +214 -0
- data/lib/wefunder_generated/models/intent_preview_envelope_data.rb +257 -0
- data/lib/wefunder_generated/models/intent_preview_envelope_data_attributes.rb +262 -0
- data/lib/wefunder_generated/models/intent_review.rb +234 -0
- data/lib/wefunder_generated/models/investment_change_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/investment_change_list_envelope_meta.rb +285 -0
- data/lib/wefunder_generated/models/investment_delta_record.rb +504 -0
- data/lib/wefunder_generated/models/investment_delta_record_amounts.rb +251 -0
- data/lib/wefunder_generated/models/investment_delta_record_blockers_inner.rb +224 -0
- data/lib/wefunder_generated/models/investment_delta_record_contracts_inner.rb +234 -0
- data/lib/wefunder_generated/models/investment_delta_record_investor.rb +286 -0
- data/lib/wefunder_generated/models/investment_envelope.rb +223 -0
- data/lib/wefunder_generated/models/investment_envelope_meta.rb +270 -0
- data/lib/wefunder_generated/models/investment_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/investment_list_envelope_meta.rb +288 -0
- data/lib/wefunder_generated/models/investment_session.rb +232 -0
- data/lib/wefunder_generated/models/investment_session_attributes.rb +384 -0
- data/lib/wefunder_generated/models/investment_session_attributes_events_inner.rb +232 -0
- data/lib/wefunder_generated/models/investment_session_create_input.rb +331 -0
- data/lib/wefunder_generated/models/investment_session_envelope.rb +223 -0
- data/lib/wefunder_generated/models/investment_session_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/investment_session_list_envelope_meta.rb +242 -0
- data/lib/wefunder_generated/models/investor.rb +233 -0
- data/lib/wefunder_generated/models/investor_attributes.rb +262 -0
- data/lib/wefunder_generated/models/investor_envelope.rb +214 -0
- data/lib/wefunder_generated/models/investor_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/invite_link.rb +233 -0
- data/lib/wefunder_generated/models/invite_link_attributes.rb +375 -0
- data/lib/wefunder_generated/models/invite_link_attributes_events_inner.rb +243 -0
- data/lib/wefunder_generated/models/invite_link_create_input.rb +295 -0
- data/lib/wefunder_generated/models/invite_link_envelope.rb +223 -0
- data/lib/wefunder_generated/models/invite_link_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/invite_link_update_input.rb +227 -0
- data/lib/wefunder_generated/models/invite_syndicate_member_request.rb +277 -0
- data/lib/wefunder_generated/models/marketing_partner.rb +295 -0
- data/lib/wefunder_generated/models/marketing_partner_envelope.rb +214 -0
- data/lib/wefunder_generated/models/member_investment.rb +234 -0
- data/lib/wefunder_generated/models/member_investment_attributes.rb +301 -0
- data/lib/wefunder_generated/models/member_investment_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/member_investment_list_envelope_meta.rb +225 -0
- data/lib/wefunder_generated/models/my_company.rb +268 -0
- data/lib/wefunder_generated/models/my_company_attributes.rb +257 -0
- data/lib/wefunder_generated/models/my_company_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/offering.rb +267 -0
- data/lib/wefunder_generated/models/offering_attributes.rb +469 -0
- data/lib/wefunder_generated/models/offering_attributes_intended_security.rb +258 -0
- data/lib/wefunder_generated/models/offering_attributes_security.rb +105 -0
- data/lib/wefunder_generated/models/offering_envelope.rb +214 -0
- data/lib/wefunder_generated/models/offering_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/offering_list_envelope_meta.rb +265 -0
- data/lib/wefunder_generated/models/offering_stats_envelope.rb +223 -0
- data/lib/wefunder_generated/models/offering_stats_envelope_data.rb +253 -0
- data/lib/wefunder_generated/models/offering_stats_envelope_data_by_status_value.rb +232 -0
- data/lib/wefunder_generated/models/offering_stats_envelope_data_total.rb +233 -0
- data/lib/wefunder_generated/models/offering_stats_envelope_meta.rb +258 -0
- data/lib/wefunder_generated/models/offering_warnings_inner.rb +257 -0
- data/lib/wefunder_generated/models/other_security.rb +267 -0
- data/lib/wefunder_generated/models/other_security_terms.rb +235 -0
- data/lib/wefunder_generated/models/pagination_meta.rb +234 -0
- data/lib/wefunder_generated/models/pagination_meta_next_cursor.rb +106 -0
- data/lib/wefunder_generated/models/partner_investment.rb +232 -0
- data/lib/wefunder_generated/models/partner_investment_attributes.rb +305 -0
- data/lib/wefunder_generated/models/partner_investment_attributes_accreditation.rb +289 -0
- data/lib/wefunder_generated/models/partner_investment_envelope.rb +214 -0
- data/lib/wefunder_generated/models/partner_investment_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/partner_investor.rb +233 -0
- data/lib/wefunder_generated/models/partner_investor_attributes.rb +264 -0
- data/lib/wefunder_generated/models/partner_investor_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/partner_invite.rb +360 -0
- data/lib/wefunder_generated/models/partner_invite_envelope.rb +214 -0
- data/lib/wefunder_generated/models/partner_invite_list_envelope.rb +216 -0
- data/lib/wefunder_generated/models/partner_webhook_event.rb +257 -0
- data/lib/wefunder_generated/models/partner_webhook_event_envelope.rb +214 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription.rb +232 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_attributes.rb +243 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_create_input.rb +239 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_envelope.rb +214 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_list_envelope.rb +216 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_with_secret.rb +239 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_with_secret_all_of_attributes.rb +215 -0
- data/lib/wefunder_generated/models/partner_webhook_subscription_with_secret_envelope.rb +214 -0
- data/lib/wefunder_generated/models/past_round.rb +309 -0
- data/lib/wefunder_generated/models/perk_tier.rb +226 -0
- data/lib/wefunder_generated/models/pitch_block.rb +375 -0
- data/lib/wefunder_generated/models/pitch_block_links_inner.rb +226 -0
- data/lib/wefunder_generated/models/portfolio_company_ref.rb +235 -0
- data/lib/wefunder_generated/models/portfolio_holding.rb +258 -0
- data/lib/wefunder_generated/models/portfolio_position.rb +268 -0
- data/lib/wefunder_generated/models/portfolio_position_attributes.rb +456 -0
- data/lib/wefunder_generated/models/portfolio_position_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/portfolio_position_list_envelope_meta.rb +234 -0
- data/lib/wefunder_generated/models/portfolio_security.rb +323 -0
- data/lib/wefunder_generated/models/portfolio_security_owner.rb +258 -0
- data/lib/wefunder_generated/models/portfolio_security_terms.rb +257 -0
- data/lib/wefunder_generated/models/portfolio_summary_envelope.rb +214 -0
- data/lib/wefunder_generated/models/portfolio_summary_envelope_data.rb +223 -0
- data/lib/wefunder_generated/models/portfolio_summary_envelope_data_attributes.rb +292 -0
- data/lib/wefunder_generated/models/portfolio_summary_envelope_data_attributes_position_counts.rb +242 -0
- data/lib/wefunder_generated/models/preview_intent_request.rb +262 -0
- data/lib/wefunder_generated/models/register_as_partner_request.rb +232 -0
- data/lib/wefunder_generated/models/reorder_syndicate_members_request.rb +224 -0
- data/lib/wefunder_generated/models/revenue_share_security.rb +266 -0
- data/lib/wefunder_generated/models/revenue_share_security_terms.rb +300 -0
- data/lib/wefunder_generated/models/safe_security.rb +266 -0
- data/lib/wefunder_generated/models/safe_security_terms.rb +257 -0
- data/lib/wefunder_generated/models/security.rb +66 -0
- data/lib/wefunder_generated/models/security_summary.rb +272 -0
- data/lib/wefunder_generated/models/spv.rb +232 -0
- data/lib/wefunder_generated/models/spv_attributes.rb +353 -0
- data/lib/wefunder_generated/models/spv_attributes_target_company.rb +223 -0
- data/lib/wefunder_generated/models/spv_cancel_intent_envelope.rb +224 -0
- data/lib/wefunder_generated/models/spv_cancel_intent_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/spv_close_intent_envelope.rb +224 -0
- data/lib/wefunder_generated/models/spv_close_intent_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/spv_create_input.rb +274 -0
- data/lib/wefunder_generated/models/spv_envelope.rb +214 -0
- data/lib/wefunder_generated/models/spv_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/spv_metrics.rb +261 -0
- data/lib/wefunder_generated/models/spv_settings.rb +275 -0
- data/lib/wefunder_generated/models/spv_status.rb +232 -0
- data/lib/wefunder_generated/models/spv_status_attributes.rb +289 -0
- data/lib/wefunder_generated/models/spv_status_envelope.rb +223 -0
- data/lib/wefunder_generated/models/spv_terms.rb +348 -0
- data/lib/wefunder_generated/models/spv_with_meta_envelope.rb +223 -0
- data/lib/wefunder_generated/models/spv_with_meta_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/syndicate.rb +234 -0
- data/lib/wefunder_generated/models/syndicate_attributes.rb +412 -0
- data/lib/wefunder_generated/models/syndicate_attributes_annual_subscription.rb +236 -0
- data/lib/wefunder_generated/models/syndicate_attributes_traditional_fund.rb +225 -0
- data/lib/wefunder_generated/models/syndicate_deal.rb +234 -0
- data/lib/wefunder_generated/models/syndicate_deal_attributes.rb +435 -0
- data/lib/wefunder_generated/models/syndicate_deal_attributes_exemption.rb +105 -0
- data/lib/wefunder_generated/models/syndicate_deal_attributes_security.rb +105 -0
- data/lib/wefunder_generated/models/syndicate_deal_close_intent_envelope.rb +224 -0
- data/lib/wefunder_generated/models/syndicate_deal_close_intent_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_deal_detail_envelope.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_deal_envelope.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_deal_finalize_intent_envelope.rb +224 -0
- data/lib/wefunder_generated/models/syndicate_deal_finalize_intent_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_deal_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/syndicate_detail_envelope.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/syndicate_member.rb +234 -0
- data/lib/wefunder_generated/models/syndicate_member_attributes.rb +650 -0
- data/lib/wefunder_generated/models/syndicate_member_envelope.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_member_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope.rb +214 -0
- data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope_data.rb +223 -0
- data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope_data_attributes.rb +302 -0
- data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope_data_attributes_deal_counts.rb +242 -0
- data/lib/wefunder_generated/models/syndicate_statistics.rb +224 -0
- data/lib/wefunder_generated/models/syndicate_statistics_attributes.rb +277 -0
- data/lib/wefunder_generated/models/syndicate_statistics_envelope.rb +214 -0
- data/lib/wefunder_generated/models/tag_ref.rb +238 -0
- data/lib/wefunder_generated/models/target_company_input.rb +258 -0
- data/lib/wefunder_generated/models/test_webhook_endpoint_request.rb +249 -0
- data/lib/wefunder_generated/models/update_syndicate_member_request.rb +221 -0
- data/lib/wefunder_generated/models/update_syndicate_member_request_member.rb +251 -0
- data/lib/wefunder_generated/models/update_syndicate_request.rb +221 -0
- data/lib/wefunder_generated/models/update_syndicate_request_syndicate.rb +232 -0
- data/lib/wefunder_generated/models/update_webhook_endpoint_request.rb +252 -0
- data/lib/wefunder_generated/models/user.rb +233 -0
- data/lib/wefunder_generated/models/user_attributes.rb +300 -0
- data/lib/wefunder_generated/models/user_envelope.rb +214 -0
- data/lib/wefunder_generated/models/validation_error.rb +215 -0
- data/lib/wefunder_generated/models/validation_error_error.rb +234 -0
- data/lib/wefunder_generated/models/validation_error_error_details_inner.rb +232 -0
- data/lib/wefunder_generated/models/webhook_delivery.rb +318 -0
- data/lib/wefunder_generated/models/webhook_endpoint.rb +232 -0
- data/lib/wefunder_generated/models/webhook_endpoint_attributes.rb +327 -0
- data/lib/wefunder_generated/models/webhook_endpoint_envelope.rb +223 -0
- data/lib/wefunder_generated/models/webhook_endpoint_envelope_meta.rb +214 -0
- data/lib/wefunder_generated/models/webhook_endpoint_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/webhook_endpoint_list_envelope_meta.rb +233 -0
- data/lib/wefunder_generated/models/webhook_endpoint_test_result_envelope.rb +223 -0
- data/lib/wefunder_generated/models/webhook_endpoint_test_result_envelope_data.rb +306 -0
- data/lib/wefunder_generated/models/webhook_subscription.rb +304 -0
- data/lib/wefunder_generated/models/webhook_subscription_envelope.rb +214 -0
- data/lib/wefunder_generated/models/webhook_subscription_list_envelope.rb +225 -0
- data/lib/wefunder_generated/models/webhook_subscription_list_envelope_meta.rb +225 -0
- data/lib/wefunder_generated/models/webhook_subscription_with_recent_deliveries_envelope.rb +214 -0
- data/lib/wefunder_generated/models/webhook_subscription_with_recent_deliveries_envelope_data.rb +322 -0
- data/lib/wefunder_generated/models/webhook_subscription_with_secret2_envelope.rb +214 -0
- data/lib/wefunder_generated/models/webhook_subscription_with_secret2_envelope_data.rb +320 -0
- data/lib/wefunder_generated/models/webhook_subscription_with_secret_envelope.rb +214 -0
- data/lib/wefunder_generated/models/webhook_subscription_with_secret_envelope_data.rb +320 -0
- data/lib/wefunder_generated/models/webhook_test_result.rb +233 -0
- data/lib/wefunder_generated/models/webhook_test_result_envelope.rb +214 -0
- data/lib/wefunder_generated/models/wefunder_round.rb +330 -0
- data/lib/wefunder_generated/models/wefunder_round_exemption.rb +104 -0
- data/lib/wefunder_generated/models/wefunder_round_security.rb +104 -0
- data/lib/wefunder_generated/version.rb +15 -0
- data/lib/wefunder_generated.rb +382 -0
- metadata +456 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8074f860a3e500b08ddab7a4fa3f7ebecb0ec937926aa52884e666243c310715
|
|
4
|
+
data.tar.gz: 22dfeff5e30c9aaa4e1def2805eab0c33795d53b69140518b364ed878b257d5c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ad2bc61f75b11d9d1164b81e4d5232f82478a28557ba018f6a7c922f4af5a451e690c05fdcbc9537a1013ea43e9f1328476252ff2f1e14ab3abcde0d48969ccc
|
|
7
|
+
data.tar.gz: 7dfd26d68fd440c078f8a0f2c5db20fe8fd44c523e85230b86f4bb83eec571f9217185fd61b70a3560f8a6d1c86a1c2fd4de3e58368e0310a32769e108fa3672
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wefunder, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# wefunder
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the [Wefunder API](https://docs.wefunder.com). **Beta** — the API is pre-launch
|
|
4
|
+
and `0.x` releases may include breaking changes.
|
|
5
|
+
|
|
6
|
+
- A `Wefunder::Client` on [Faraday](https://lostisland.github.io/faraday/) with a generated, typed
|
|
7
|
+
layer for every public operation
|
|
8
|
+
- OAuth 2.0: `client_credentials` for server-to-server, `authorization_code` + PKCE for user consent,
|
|
9
|
+
refresh-token rotation handled for you
|
|
10
|
+
- Automatic retries (rate limits, transient errors) and one-shot token recovery on 401
|
|
11
|
+
- Lazy auto-pagination over opaque cursors
|
|
12
|
+
- Typed errors with the `request_id` support asks for
|
|
13
|
+
- Webhook signature verification, envelope parsing, and dispatch
|
|
14
|
+
|
|
15
|
+
Requires Ruby 3.2+.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install wefunder --pre
|
|
21
|
+
# or in a Gemfile:
|
|
22
|
+
gem "wefunder", "~> 0.1.0.beta"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`--pre` is needed while the SDK is in beta (pre-release gems are not selected by default).
|
|
26
|
+
|
|
27
|
+
## Authentication
|
|
28
|
+
|
|
29
|
+
### Server-to-server
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require "wefunder"
|
|
33
|
+
|
|
34
|
+
wf = Wefunder::Client.from_client_credentials(
|
|
35
|
+
client_id: ENV.fetch("WEFUNDER_CLIENT_ID"),
|
|
36
|
+
client_secret: ENV.fetch("WEFUNDER_CLIENT_SECRET"),
|
|
37
|
+
scopes: ["read:public"]
|
|
38
|
+
)
|
|
39
|
+
page = wf.offerings.list
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Client-credentials tokens represent the application, not a user. They cannot call user-scoped
|
|
43
|
+
endpoints such as `wf.users.me` or `wf.portfolio.get`. The SDK mints a new token automatically when a
|
|
44
|
+
client-credentials token expires.
|
|
45
|
+
|
|
46
|
+
### User authorization with PKCE
|
|
47
|
+
|
|
48
|
+
Generate the authorization URL on your server; store `state` and the PKCE verifier in the user's
|
|
49
|
+
session before redirecting:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
pkce = Wefunder.generate_pkce
|
|
53
|
+
state = SecureRandom.urlsafe_base64(32)
|
|
54
|
+
save_oauth_attempt(state: state, code_verifier: pkce.code_verifier)
|
|
55
|
+
|
|
56
|
+
url = Wefunder.create_authorization_url(
|
|
57
|
+
client_id: client_id, redirect_uri: redirect_uri, scopes: ["read:investments"], state: state, pkce: pkce
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The consent host is chosen from the `client_id`: `pk_test_` ids go to the sandbox, everything else to
|
|
62
|
+
wefunder.com. On the callback, validate `state` and exchange the code:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
attempt = consume_oauth_attempt(state)
|
|
66
|
+
tokens = Wefunder.exchange_code(
|
|
67
|
+
client_id: client_id, client_secret: client_secret, # omit for public clients
|
|
68
|
+
code: params[:code], redirect_uri: redirect_uri, code_verifier: attempt.code_verifier
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
wf = Wefunder::Client.new(tokens: tokens, client_id: client_id, client_secret: client_secret, store: TokenStore.new)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Refresh tokens
|
|
75
|
+
|
|
76
|
+
Refresh tokens rotate. When the SDK refreshes an access token it calls `store.save(tokens)` with the
|
|
77
|
+
new set **before** retrying the request. Persist the entire token set each time:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
class TokenStore
|
|
81
|
+
def save(tokens)
|
|
82
|
+
DB.save_tokens(tokens.access_token, tokens.refresh_token, tokens.expires_at)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Concurrent requests that hit a 401 at the same time share one refresh (the client is thread-safe).
|
|
88
|
+
If several application instances can use the same OAuth connection, serialize refreshes for that
|
|
89
|
+
connection yourself.
|
|
90
|
+
|
|
91
|
+
## Calling the API
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
offerings = wf.offerings.list(sort: "newest")
|
|
95
|
+
investments = wf.investments.list(company_id: "co_example")
|
|
96
|
+
portfolio = wf.portfolio.get
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Namespaces: `users`, `offerings`, `investments`, `portfolio`, `campaigns`, `syndicates`, `intents`,
|
|
100
|
+
`attribution`, and `webhook_endpoints`. Query parameters are keyword arguments; enum-typed ones take
|
|
101
|
+
plain strings.
|
|
102
|
+
|
|
103
|
+
`wf.investments` is the Investment Delta API. `list` without a cursor bootstraps; pass `updated_since:`
|
|
104
|
+
or the `meta.next_cursor` you saved from your last page to receive only records that changed since then.
|
|
105
|
+
`next_cursor` is always present, even on the final page, so persist it after every sync.
|
|
106
|
+
|
|
107
|
+
The API base URL is `https://api.wefunder.com`. Paths are version-free; the SDK sends the API version in
|
|
108
|
+
the `Wefunder-Version` request header.
|
|
109
|
+
|
|
110
|
+
## Pagination
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
page = wf.offerings.list(sort: "newest") # one page, and its cursor
|
|
114
|
+
puts page.meta.next_cursor
|
|
115
|
+
|
|
116
|
+
wf.offerings.all(sort: "most_raised").each do |offering| # every item, lazily (an Enumerator)
|
|
117
|
+
puts offering.id
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
investments = wf.investments.collect # everything in one Array
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Cursors are opaque. Pass the value returned by the API without modifying it.
|
|
124
|
+
|
|
125
|
+
## Errors and retries
|
|
126
|
+
|
|
127
|
+
API failures raise `Wefunder::Error`:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
begin
|
|
131
|
+
wf.syndicates.get("syn_example")
|
|
132
|
+
rescue Wefunder::Error => e
|
|
133
|
+
puts e.status, e.type, e.message, e.request_id, e.remediation
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The SDK retries idempotent `GET` requests after transient network errors, `5xx` responses, and rate
|
|
138
|
+
limits (honouring `X-RateLimit-Reset`). Write requests are not retried automatically, except once after
|
|
139
|
+
a `401` has been recovered.
|
|
140
|
+
|
|
141
|
+
## Webhooks
|
|
142
|
+
|
|
143
|
+
### 1. Register an endpoint
|
|
144
|
+
|
|
145
|
+
Endpoints belong to your application and are managed through the live API (scope `write:webhooks`).
|
|
146
|
+
The signing secret is returned only on create and rotate, so store it immediately.
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
endpoint = wf.webhook_endpoints.create(
|
|
150
|
+
url: "https://yourapp.com/webhooks/wefunder", # public HTTPS; localhost and private IPs are rejected
|
|
151
|
+
events: ["offering.opened", "investment.executed"],
|
|
152
|
+
mode: "live" # "test" endpoints receive sandbox events
|
|
153
|
+
)
|
|
154
|
+
save_secret(endpoint.attributes.secret)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`wf.webhook_endpoints` also provides `list`, `get`, `update`, `remove`, `rotate_secret`, `reenable`,
|
|
158
|
+
and `test`.
|
|
159
|
+
|
|
160
|
+
### 2. Verify and handle deliveries
|
|
161
|
+
|
|
162
|
+
Pass the **raw** request body, the headers (a Rack env works), and your secret to
|
|
163
|
+
`Wefunder.construct_event`. It raises `Wefunder::WebhookSignatureError` (with a `reason`) when a
|
|
164
|
+
delivery is not authentic.
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
post "/webhooks/wefunder" do
|
|
168
|
+
begin
|
|
169
|
+
event = Wefunder.construct_event(request.body.read, request.env, WEBHOOK_SECRET)
|
|
170
|
+
rescue Wefunder::WebhookSignatureError => e
|
|
171
|
+
halt 400, e.reason
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
Wefunder.dispatch_webhook(event, {
|
|
175
|
+
"investment.executed" => ->(e) { record_funding(e.data["id"], e.data.dig("amounts", "committed")) },
|
|
176
|
+
"offering.opened" => ->(e) { announce(e.data.dig("company", "name")) },
|
|
177
|
+
default: ->(e) { logger.info("unhandled #{e.event}") }
|
|
178
|
+
})
|
|
179
|
+
200
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Deliveries are at-least-once and unordered. Deduplicate on `event.id`, and where a payload carries
|
|
184
|
+
`occurred_at`, keep the state from the latest one you have seen.
|
|
185
|
+
|
|
186
|
+
### 3. Test your handler
|
|
187
|
+
|
|
188
|
+
`wf.webhook_endpoints.test(endpoint.id)` sends a real, signed example event and reports the outcome.
|
|
189
|
+
To unit-test your handler without the API, sign a fixture yourself:
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
header = Wefunder.sign_webhook(body, secret)
|
|
193
|
+
# POST `body` to your handler with `Wefunder-Signature: <header>`
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Signature scheme
|
|
197
|
+
|
|
198
|
+
Each delivery carries `Wefunder-Signature: t=<unix seconds>,v1=<hex>` where `v1` is
|
|
199
|
+
`HMAC-SHA256(secret, "<t>.<raw body>")`. Requests whose `t` is more than five minutes from now are
|
|
200
|
+
rejected (`tolerance_seconds:` adjusts this). During a secret rotation the header carries one `v1` per
|
|
201
|
+
active secret and `construct_event` accepts either. `Wefunder.verify_webhook` and
|
|
202
|
+
`Wefunder.check_webhook_signature` expose the check without parsing, and `construct_event` still accepts
|
|
203
|
+
the retired attribution headers (`X-Wefunder-Signature` / `X-Wefunder-Timestamp`).
|
|
204
|
+
|
|
205
|
+
## Generated operations
|
|
206
|
+
|
|
207
|
+
Typed namespaces cover the common resources. Every operation in the public OpenAPI specification is
|
|
208
|
+
generated under `WefunderGenerated::<Tag>Api` and reachable through `wf.raw.<tag>`; wrap the call to get
|
|
209
|
+
the same auth, retries, and typed errors:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
members = wf.wrap { wf.raw.syndicate_members.list_syndicate_members("syn_example") }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
For a path the generated layer does not know yet, `wf.request(:get, path, query:, body:, headers:)`
|
|
216
|
+
sends a fully-wrapped request and returns the decoded JSON.
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
bundle install
|
|
222
|
+
bundle exec rubocop
|
|
223
|
+
bundle exec rspec # hermetic; spec/e2e is excluded by rake and CI on PRs
|
|
224
|
+
bundle exec rspec spec/e2e # live sandbox, with WEFUNDER_CLIENT_ID / WEFUNDER_CLIENT_SECRET
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Generated files in `lib/wefunder_generated/` come from `openapi/openapi.yaml` via `script/generate`
|
|
228
|
+
(openapi-generator in Docker; no Java needed) and are never edited by hand.
|
|
229
|
+
|
|
230
|
+
### Conformance vectors
|
|
231
|
+
|
|
232
|
+
`conformance/*.json` is the cross-language behavioural contract shared with `wefunder-node` and
|
|
233
|
+
`wefunder-python`: signatures, token rotation, pagination, retries, errors. `spec/conformance_spec.rb`
|
|
234
|
+
runs every case. The files are vendored from `Wefunder/wefunder-node` at the ref in `conformance/PIN`;
|
|
235
|
+
refresh with `ruby script/sync_conformance.rb [ref]`. Never edit a vector to make the shell pass.
|
|
236
|
+
|
|
237
|
+
### Examples
|
|
238
|
+
|
|
239
|
+
`examples/` is the source of truth for the Ruby snippets on docs.wefunder.com; see
|
|
240
|
+
`examples/README.md`. After adding one, run `ruby script/build_examples_manifest.rb` and remove its
|
|
241
|
+
`operationId` from `examples/coverage-allowlist.json`.
|
|
242
|
+
|
|
243
|
+
### Updating the API specification
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
script/sync_spec /path/to/wefunder # public-tier spec → openapi/openapi.yaml
|
|
247
|
+
script/generate # → lib/wefunder_generated
|
|
248
|
+
bundle exec rspec
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Commit the specification and generated client together.
|
|
252
|
+
|
|
253
|
+
### Releasing
|
|
254
|
+
|
|
255
|
+
Set the version in `lib/wefunder/version.rb` (e.g. `0.1.0.beta2`), rebuild `examples_manifest.json`,
|
|
256
|
+
commit, then:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
git tag v0.1.0.beta2
|
|
260
|
+
git push --follow-tags
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The release workflow verifies the tag, runs the checks, and publishes to RubyGems via trusted publishing.
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "faraday"
|
|
6
|
+
|
|
7
|
+
module Wefunder
|
|
8
|
+
# Version-free base — the edge gateway serves the API at the host root; +/api/v2+ remains a
|
|
9
|
+
# back-compat alias. The API version is pinned via the +Wefunder-Version+ header, not the path.
|
|
10
|
+
DEFAULT_API_BASE_URL = "https://api.wefunder.com"
|
|
11
|
+
DEFAULT_API_VERSION = "2025-01-15"
|
|
12
|
+
API_VERSION_HEADER = "Wefunder-Version"
|
|
13
|
+
|
|
14
|
+
# "live" / "test" from the access-token prefix. UX only — never used for routing.
|
|
15
|
+
def self.mode_for_token(token)
|
|
16
|
+
return "live" if token.to_s.start_with?("at_live_")
|
|
17
|
+
return "test" if token.to_s.start_with?("at_test_")
|
|
18
|
+
|
|
19
|
+
"unknown"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The Wefunder client — the stable, hand-written surface over the generated layer
|
|
23
|
+
# (+WefunderGenerated+). Wires: a single API host (the gateway routes live/sandbox by token
|
|
24
|
+
# mode), a pinned +Wefunder-Version+, the retry/refresh transport, token rotation,
|
|
25
|
+
# typed-error unwrapping, and lazy auto-pagination. +raw+ exposes every generated API.
|
|
26
|
+
class Client
|
|
27
|
+
attr_reader :mode, :users, :offerings, :investments, :portfolio, :campaigns, :syndicates, :intents,
|
|
28
|
+
:attribution, :webhook_endpoints
|
|
29
|
+
|
|
30
|
+
def initialize(access_token: nil, tokens: nil, client_id: nil, client_secret: nil, client_credentials: nil,
|
|
31
|
+
api_version: DEFAULT_API_VERSION, base_url: DEFAULT_API_BASE_URL, authorize_base_url: nil,
|
|
32
|
+
token_base_url: nil, oauth_base_url: nil, store: nil, on_token_refresh: nil, retry_options: nil,
|
|
33
|
+
faraday: nil, now: nil, sleep: nil, random: nil, timeout: 30)
|
|
34
|
+
token_set = tokens || (access_token && TokenSet.new(access_token: access_token))
|
|
35
|
+
raise ArgumentError, "Wefunder::Client: provide access_token: or tokens:" unless token_set
|
|
36
|
+
|
|
37
|
+
_ = authorize_base_url # only /authorize uses it; see OAuth.create_authorization_url
|
|
38
|
+
@mode = Wefunder.mode_for_token(token_set.access_token)
|
|
39
|
+
token_base = OAuth.resolve_token_base(token_base_url: token_base_url, oauth_base_url: oauth_base_url)
|
|
40
|
+
re_mint = build_re_mint(client_credentials, client_id, client_secret, token_base, faraday, now)
|
|
41
|
+
@token_manager = TokenManager.new(token_set, client_id: client_id, client_secret: client_secret, re_mint: re_mint,
|
|
42
|
+
on_token_refresh: on_token_refresh, store: store, faraday: faraday, now: now,
|
|
43
|
+
token_base_url: token_base)
|
|
44
|
+
@api_client = build_api_client(base_url, api_version, retry_options, faraday, now, sleep, random, timeout)
|
|
45
|
+
@raw = Raw.new(@api_client)
|
|
46
|
+
@users = Users.new(self)
|
|
47
|
+
@offerings = Offerings.new(self)
|
|
48
|
+
@investments = Investments.new(self)
|
|
49
|
+
@portfolio = Portfolio.new(self)
|
|
50
|
+
@campaigns = Campaigns.new(self)
|
|
51
|
+
@syndicates = Syndicates.new(self)
|
|
52
|
+
@intents = Intents.new(self)
|
|
53
|
+
@attribution = Attribution.new(self)
|
|
54
|
+
@webhook_endpoints = WebhookEndpoints.new(self)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Mint an application token now and auto-re-mint on expiry/401 (cc tokens have no refresh
|
|
58
|
+
# token). Extra options are forwarded to +new+.
|
|
59
|
+
def self.from_client_credentials(client_id:, client_secret:, scopes: nil, **opts)
|
|
60
|
+
tokens = OAuth.client_credentials_grant(
|
|
61
|
+
client_id: client_id, client_secret: client_secret, scopes: scopes,
|
|
62
|
+
token_base_url: opts[:token_base_url], oauth_base_url: opts[:oauth_base_url], faraday: opts[:faraday], now: opts[:now]
|
|
63
|
+
)
|
|
64
|
+
new(tokens: tokens, client_id: client_id, client_secret: client_secret, client_credentials: { scopes: scopes }, **opts)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The current token set (rotated refresh token included).
|
|
68
|
+
def tokens = @token_manager.current
|
|
69
|
+
|
|
70
|
+
# The generated APIs, one accessor per tag (+wf.raw.syndicate_members.list_syndicate_members(id)+).
|
|
71
|
+
# Wrap calls in +wrap+ to get typed errors.
|
|
72
|
+
attr_reader :raw
|
|
73
|
+
|
|
74
|
+
# Run a block of generated-API calls, converting +WefunderGenerated::ApiError+ into +Wefunder::Error+.
|
|
75
|
+
def wrap
|
|
76
|
+
yield
|
|
77
|
+
rescue WefunderGenerated::ApiError => e
|
|
78
|
+
raise Error.new(status: 0, type: "network_error", message: e.message.to_s) if e.code.nil? || e.code.zero?
|
|
79
|
+
|
|
80
|
+
raise Error.from_response(e.code, e.response_headers, e.response_body, reason_phrase: e.message)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Escape hatch for any path (preview ops, brand-new endpoints): full envelope — auth +
|
|
84
|
+
# recovery, Wefunder-Version, retries, typed errors. Returns parsed JSON (nil for an empty
|
|
85
|
+
# body). +body+ is sent as JSON unless it is a String.
|
|
86
|
+
def request(method, path, query: nil, body: nil, headers: nil)
|
|
87
|
+
conn = @api_client.connection(header_params: {})
|
|
88
|
+
payload = body.nil? || body.is_a?(String) ? body : JSON.generate(body)
|
|
89
|
+
hdrs = @api_client.default_headers.merge(headers || {})
|
|
90
|
+
response = conn.run_request(method.to_s.downcase.to_sym, path, payload, hdrs) do |req|
|
|
91
|
+
req.params.update((query || {}).compact.transform_keys(&:to_s))
|
|
92
|
+
end
|
|
93
|
+
if response.status >= 400
|
|
94
|
+
raise Error.from_response(response.status, response.headers, response.body,
|
|
95
|
+
reason_phrase: response.reason_phrase)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
response.body.to_s.empty? ? nil : JSON.parse(response.body)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Single-resource envelopes are +{data: Entity}+; return the entity for ergonomics.
|
|
102
|
+
def self.data_of(envelope)
|
|
103
|
+
return envelope["data"] || envelope[:data] if envelope.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
envelope.respond_to?(:data) ? envelope.data : envelope
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def build_re_mint(client_credentials, client_id, client_secret, token_base, faraday, now)
|
|
111
|
+
return nil unless client_credentials && client_id && client_secret
|
|
112
|
+
|
|
113
|
+
scopes = client_credentials[:scopes] || client_credentials["scopes"]
|
|
114
|
+
lambda do
|
|
115
|
+
OAuth.client_credentials_grant(client_id: client_id, client_secret: client_secret, scopes: scopes,
|
|
116
|
+
token_base_url: token_base, faraday: faraday, now: now)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_api_client(base_url, api_version, retry_opts, faraday, now, sleep, random, timeout)
|
|
121
|
+
config = WefunderGenerated::Configuration.new
|
|
122
|
+
uri = URI(base_url)
|
|
123
|
+
config.scheme = uri.scheme
|
|
124
|
+
config.host = uri.port && uri.port != uri.default_port ? "#{uri.host}:#{uri.port}" : uri.host
|
|
125
|
+
config.base_path = uri.path.to_s
|
|
126
|
+
config.server_index = nil
|
|
127
|
+
# The transport owns the Authorization header (it reads the managed token per attempt),
|
|
128
|
+
# so the generated client's own token is a placeholder.
|
|
129
|
+
config.access_token = "managed"
|
|
130
|
+
config.timeout = timeout
|
|
131
|
+
tm = @token_manager
|
|
132
|
+
now_ms = now && -> { now.call * 1000 }
|
|
133
|
+
config.configure_faraday_connection do |conn|
|
|
134
|
+
conn.use Transport, token_manager: tm, retry_options: retry_opts, sleep: sleep, now_ms: now_ms, random: random
|
|
135
|
+
faraday&.call(conn)
|
|
136
|
+
end
|
|
137
|
+
client = WefunderGenerated::ApiClient.new(config)
|
|
138
|
+
client.default_headers[API_VERSION_HEADER] = api_version
|
|
139
|
+
client
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Lazily-built generated API instances, one per tag.
|
|
143
|
+
class Raw
|
|
144
|
+
APIS = {
|
|
145
|
+
activity: "ActivityApi", attribution: "AttributionApi", attribution_partners: "AttributionPartnersApi",
|
|
146
|
+
attribution_webhooks: "AttributionWebhooksApi", campaigns: "CampaignsApi", companies: "CompaniesApi",
|
|
147
|
+
explore: "ExploreApi", installations: "InstallationsApi", intents: "IntentsApi",
|
|
148
|
+
investment_delta: "InvestmentDeltaApi", investments: "InvestmentsApi", portfolio: "PortfolioApi",
|
|
149
|
+
syndicate_deals: "SyndicateDealsApi", syndicate_members: "SyndicateMembersApi",
|
|
150
|
+
syndicate_portfolio: "SyndicatePortfolioApi", syndicate_statistics: "SyndicateStatisticsApi",
|
|
151
|
+
syndicates: "SyndicatesApi", users: "UsersApi", webhook_endpoints: "WebhookEndpointsApi"
|
|
152
|
+
}.freeze
|
|
153
|
+
|
|
154
|
+
def initialize(api_client)
|
|
155
|
+
@api_client = api_client
|
|
156
|
+
@apis = {}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
APIS.each do |name, klass|
|
|
160
|
+
define_method(name) { @apis[name] ||= WefunderGenerated.const_get(klass).new(@api_client) }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Base for the resource namespaces.
|
|
165
|
+
class Namespace
|
|
166
|
+
def initialize(client)
|
|
167
|
+
@client = client
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def wrap(&) = @client.wrap(&)
|
|
173
|
+
def raw = @client.raw
|
|
174
|
+
def data_of(env) = Client.data_of(env)
|
|
175
|
+
|
|
176
|
+
# Auto-paginate a list method, keeping +opts+ on every page and threading the cursor.
|
|
177
|
+
def pages(opts, &fetch)
|
|
178
|
+
Wefunder.paginate { |cursor| wrap { fetch.call(cursor.nil? ? opts : opts.merge(cursor: cursor)) } }
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class Users < Namespace
|
|
183
|
+
# GET /users/me (needs read:profile; a client_credentials token gets 403).
|
|
184
|
+
def me = data_of(wrap { raw.users.get_current_user })
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class Offerings < Namespace
|
|
188
|
+
# One page of GET /explore (+sort:+, +cursor:+, filters). Returns the envelope.
|
|
189
|
+
def list(**opts) = wrap { raw.explore.list_offerings(opts) }
|
|
190
|
+
# Every offering, lazily, +opts+ preserved across pages.
|
|
191
|
+
def all(**opts) = pages(opts) { |o| raw.explore.list_offerings(o) }
|
|
192
|
+
def get(offering_id) = data_of(wrap { raw.explore.get_offering(offering_id) })
|
|
193
|
+
def stats(offering_id) = data_of(wrap { raw.investments.get_offering_stats(offering_id) })
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The Investment Delta API. +list+ without a cursor bootstraps; +next_cursor+ is always
|
|
197
|
+
# present (persist it after every sync) and +has_more+ terminates.
|
|
198
|
+
class Investments < Namespace
|
|
199
|
+
def list(**opts) = wrap { raw.investments.list_investments(opts) }
|
|
200
|
+
def all(**opts) = pages(opts) { |o| raw.investments.list_investments(o) }
|
|
201
|
+
def collect(**) = all(**).to_a
|
|
202
|
+
def get(investment_id) = data_of(wrap { raw.investments.get_investment(investment_id) })
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
class Portfolio < Namespace
|
|
206
|
+
Positions = Class.new(Namespace) do
|
|
207
|
+
def list(**opts) = wrap { raw.portfolio.list_portfolio_positions(opts) }
|
|
208
|
+
def all(**opts) = pages(opts) { |o| raw.portfolio.list_portfolio_positions(o) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
attr_reader :positions
|
|
212
|
+
|
|
213
|
+
def initialize(client)
|
|
214
|
+
super
|
|
215
|
+
@positions = Positions.new(client)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# The portfolio summary (+status:+, +company:+ filters).
|
|
219
|
+
def get(**opts) = data_of(wrap { raw.portfolio.get_portfolio(opts) })
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
class Campaigns < Namespace
|
|
223
|
+
def list(**opts) = wrap { raw.campaigns.list_campaigns(opts) }
|
|
224
|
+
def all(**opts) = pages(opts) { |o| raw.campaigns.list_campaigns(o) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
class Syndicates < Namespace
|
|
228
|
+
def list(**opts) = wrap { raw.syndicates.list_syndicates(opts) }
|
|
229
|
+
def all(**opts) = pages(opts) { |o| raw.syndicates.list_syndicates(o) }
|
|
230
|
+
def get(syndicate_id) = data_of(wrap { raw.syndicates.get_syndicate(syndicate_id) })
|
|
231
|
+
def portfolio(syndicate_id, **opts) = data_of(wrap { raw.syndicate_portfolio.get_syndicate_portfolio(syndicate_id, opts) })
|
|
232
|
+
|
|
233
|
+
def portfolio_positions(syndicate_id, **opts)
|
|
234
|
+
pages(opts) { |o| raw.syndicate_portfolio.list_syndicate_portfolio_positions(syndicate_id, o) }
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
class Intents < Namespace
|
|
239
|
+
def list(**opts) = wrap { raw.intents.list_intents(opts) }
|
|
240
|
+
def all(**opts) = pages(opts) { |o| raw.intents.list_intents(o) }
|
|
241
|
+
def get(intent_id) = data_of(wrap { raw.intents.get_intent(intent_id) })
|
|
242
|
+
def create(body) = data_of(wrap { raw.intents.create_intent(WefunderGenerated::CreateIntentRequest.new(body)) })
|
|
243
|
+
def preview(body) = data_of(wrap { raw.intents.preview_intent(WefunderGenerated::PreviewIntentRequest.new(body)) })
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
class Attribution < Namespace
|
|
247
|
+
def me = data_of(wrap { raw.attribution_partners.get_attribution_me })
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Endpoints belong to your application and are managed through the LIVE API
|
|
251
|
+
# (write:webhooks). The signing secret is returned only on create and rotate.
|
|
252
|
+
class WebhookEndpoints < Namespace
|
|
253
|
+
def create(url:, events:, mode: nil)
|
|
254
|
+
attrs = { url: url, events: Array(events) }
|
|
255
|
+
attrs[:mode] = mode if mode
|
|
256
|
+
data_of(wrap do
|
|
257
|
+
raw.webhook_endpoints.create_webhook_endpoint(WefunderGenerated::CreateWebhookEndpointRequest.new(attrs))
|
|
258
|
+
end)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The list envelope (+meta.quota+ is the max endpoints per app).
|
|
262
|
+
def list = wrap { raw.webhook_endpoints.list_webhook_endpoints }
|
|
263
|
+
def get(endpoint_id) = data_of(wrap { raw.webhook_endpoints.get_webhook_endpoint(endpoint_id) })
|
|
264
|
+
|
|
265
|
+
# +events+ replaces the list wholesale.
|
|
266
|
+
def update(endpoint_id, **changes)
|
|
267
|
+
body = WefunderGenerated::UpdateWebhookEndpointRequest.new(changes)
|
|
268
|
+
data_of(wrap { raw.webhook_endpoints.update_webhook_endpoint(endpoint_id, update_webhook_endpoint_request: body) })
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def remove(endpoint_id) = data_of(wrap { raw.webhook_endpoints.delete_webhook_endpoint(endpoint_id) })
|
|
272
|
+
# A new secret; the old one keeps signing for 24h and deliveries carry both v1s.
|
|
273
|
+
def rotate_secret(endpoint_id) = data_of(wrap { raw.webhook_endpoints.rotate_webhook_endpoint_secret(endpoint_id) })
|
|
274
|
+
def reenable(endpoint_id) = data_of(wrap { raw.webhook_endpoints.reenable_webhook_endpoint(endpoint_id) })
|
|
275
|
+
|
|
276
|
+
# Send a real, signed example event to the endpoint and report the outcome.
|
|
277
|
+
def test(endpoint_id, event = nil)
|
|
278
|
+
opts = event ? { test_webhook_endpoint_request: WefunderGenerated::TestWebhookEndpointRequest.new(event: event) } : {}
|
|
279
|
+
data_of(wrap { raw.webhook_endpoints.test_webhook_endpoint(endpoint_id, opts) })
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Wefunder
|
|
6
|
+
# Response header carrying the partner-facing request id (Stripe-style +req_…+).
|
|
7
|
+
REQUEST_ID_HEADER = "X-Wf-Request-Id"
|
|
8
|
+
# +type+ reported when the body is not the documented envelope.
|
|
9
|
+
FALLBACK_ERROR_TYPE = "api_error"
|
|
10
|
+
|
|
11
|
+
# An API call failed. Mapped from the REAL error envelope
|
|
12
|
+
# (api/v2/base_controller.rb#render_error):
|
|
13
|
+
# {"error": {"type", "message", "details"?, "request_id", "remediation"?}}
|
|
14
|
+
# +request_id+ and +remediation+ are NESTED under +error+. The +X-Wf-Request-Id+ response
|
|
15
|
+
# header is set on every authenticated response — even an HTML 502 from the edge — so it
|
|
16
|
+
# is preferred over the body's id.
|
|
17
|
+
class Error < StandardError
|
|
18
|
+
DOCUMENTATION_URL = "https://docs.wefunder.com/api-reference"
|
|
19
|
+
|
|
20
|
+
attr_reader :status, :type, :request_id, :details, :remediation
|
|
21
|
+
|
|
22
|
+
def initialize(status:, type:, message:, request_id: nil, details: nil, remediation: nil)
|
|
23
|
+
super(message)
|
|
24
|
+
@status = status
|
|
25
|
+
@type = type
|
|
26
|
+
@request_id = request_id
|
|
27
|
+
@details = details
|
|
28
|
+
@remediation = remediation
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# One-line summary for logs. (+message+ stays the server's message verbatim — in Ruby
|
|
32
|
+
# +Exception#message+ delegates to +to_s+, so this is deliberately NOT a +to_s+ override.)
|
|
33
|
+
def summary
|
|
34
|
+
parts = ["#{status} #{type}: #{message}"]
|
|
35
|
+
parts << "(request_id=#{request_id})" if request_id
|
|
36
|
+
parts << "— #{remediation}" if remediation
|
|
37
|
+
parts.join(" ")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inspect = "#<#{self.class.name} #{summary}>"
|
|
41
|
+
|
|
42
|
+
# Prefer the +X-Wf-Request-Id+ header; fall back to a body-derived id.
|
|
43
|
+
def self.request_id_from(headers, body_request_id)
|
|
44
|
+
header = (headers || {}).find { |k, _| k.to_s.casecmp?(REQUEST_ID_HEADER) }&.last
|
|
45
|
+
header = header.first if header.is_a?(Array)
|
|
46
|
+
(header.nil? || header.empty? ? nil : header) || body_request_id
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Build an Error from a failed response's parts. Degrades gracefully when the body is
|
|
50
|
+
# not the documented shape (non-JSON, empty, or a bare object).
|
|
51
|
+
def self.from_response(status, headers, body, reason_phrase: nil)
|
|
52
|
+
parsed = parse_body(body)
|
|
53
|
+
err = parsed.is_a?(Hash) && parsed["error"].is_a?(Hash) ? parsed["error"] : {}
|
|
54
|
+
top_level_request_id = parsed.is_a?(Hash) ? parsed["request_id"] : nil
|
|
55
|
+
message = if err["message"].is_a?(String)
|
|
56
|
+
err["message"]
|
|
57
|
+
else
|
|
58
|
+
(reason_phrase.to_s.empty? ? "Request failed" : reason_phrase)
|
|
59
|
+
end
|
|
60
|
+
new(
|
|
61
|
+
status: status.to_i,
|
|
62
|
+
type: err["type"].is_a?(String) ? err["type"] : FALLBACK_ERROR_TYPE,
|
|
63
|
+
message: message,
|
|
64
|
+
request_id: request_id_from(headers, err["request_id"] || top_level_request_id),
|
|
65
|
+
details: err["details"],
|
|
66
|
+
remediation: err["remediation"].is_a?(String) ? err["remediation"] : nil
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.parse_body(body)
|
|
71
|
+
return body if body.is_a?(Hash)
|
|
72
|
+
return nil if body.nil? || body.to_s.empty?
|
|
73
|
+
|
|
74
|
+
JSON.parse(body.to_s)
|
|
75
|
+
rescue JSON::ParserError
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
private_class_method :parse_body
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Raised when a token must be recovered but no refresh token / re-mint capability exists.
|
|
82
|
+
class AuthError < Error
|
|
83
|
+
def initialize(message)
|
|
84
|
+
super(status: 401, type: "unauthorized", message: message)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|