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.
Files changed (361) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +263 -0
  4. data/lib/wefunder/client.rb +283 -0
  5. data/lib/wefunder/errors.rb +87 -0
  6. data/lib/wefunder/oauth.rb +135 -0
  7. data/lib/wefunder/pagination.rb +52 -0
  8. data/lib/wefunder/token_manager.rb +80 -0
  9. data/lib/wefunder/transport.rb +93 -0
  10. data/lib/wefunder/version.rb +5 -0
  11. data/lib/wefunder/webhooks.rb +238 -0
  12. data/lib/wefunder.rb +26 -0
  13. data/lib/wefunder_generated/api/activity_api.rb +234 -0
  14. data/lib/wefunder_generated/api/attribution_api.rb +182 -0
  15. data/lib/wefunder_generated/api/attribution_partners_api.rb +392 -0
  16. data/lib/wefunder_generated/api/attribution_webhooks_api.rb +504 -0
  17. data/lib/wefunder_generated/api/campaigns_api.rb +82 -0
  18. data/lib/wefunder_generated/api/companies_api.rb +101 -0
  19. data/lib/wefunder_generated/api/explore_api.rb +633 -0
  20. data/lib/wefunder_generated/api/installations_api.rb +405 -0
  21. data/lib/wefunder_generated/api/intents_api.rb +301 -0
  22. data/lib/wefunder_generated/api/investment_delta_api.rb +99 -0
  23. data/lib/wefunder_generated/api/investments_api.rb +238 -0
  24. data/lib/wefunder_generated/api/portfolio_api.rb +166 -0
  25. data/lib/wefunder_generated/api/syndicate_deals_api.rb +381 -0
  26. data/lib/wefunder_generated/api/syndicate_members_api.rb +976 -0
  27. data/lib/wefunder_generated/api/syndicate_portfolio_api.rb +178 -0
  28. data/lib/wefunder_generated/api/syndicate_statistics_api.rb +85 -0
  29. data/lib/wefunder_generated/api/syndicates_api.rb +226 -0
  30. data/lib/wefunder_generated/api/users_api.rb +329 -0
  31. data/lib/wefunder_generated/api/webhook_endpoints_api.rb +539 -0
  32. data/lib/wefunder_generated/api_client.rb +437 -0
  33. data/lib/wefunder_generated/api_error.rb +58 -0
  34. data/lib/wefunder_generated/configuration.rb +392 -0
  35. data/lib/wefunder_generated/models/anonymized_attribution.rb +298 -0
  36. data/lib/wefunder_generated/models/anonymized_attribution_attribution.rb +330 -0
  37. data/lib/wefunder_generated/models/anonymized_attribution_status.rb +260 -0
  38. data/lib/wefunder_generated/models/attributed_investment_list_envelope.rb +226 -0
  39. data/lib/wefunder_generated/models/attributed_investment_list_envelope_data_inner.rb +105 -0
  40. data/lib/wefunder_generated/models/attributed_investment_list_envelope_meta.rb +295 -0
  41. data/lib/wefunder_generated/models/attribution_campaign_access.rb +241 -0
  42. data/lib/wefunder_generated/models/attribution_campaign_summary.rb +251 -0
  43. data/lib/wefunder_generated/models/attribution_campaign_summary_list_envelope.rb +216 -0
  44. data/lib/wefunder_generated/models/attribution_me.rb +235 -0
  45. data/lib/wefunder_generated/models/attribution_me_envelope.rb +214 -0
  46. data/lib/wefunder_generated/models/attribution_partner.rb +275 -0
  47. data/lib/wefunder_generated/models/attribution_stats.rb +266 -0
  48. data/lib/wefunder_generated/models/attribution_stats_by_campaign_inner.rb +232 -0
  49. data/lib/wefunder_generated/models/attribution_stats_by_source_inner.rb +232 -0
  50. data/lib/wefunder_generated/models/attribution_stats_envelope.rb +214 -0
  51. data/lib/wefunder_generated/models/attribution_stats_period.rb +223 -0
  52. data/lib/wefunder_generated/models/attribution_stats_quality_breakdown.rb +236 -0
  53. data/lib/wefunder_generated/models/attribution_stats_totals.rb +245 -0
  54. data/lib/wefunder_generated/models/attribution_user.rb +232 -0
  55. data/lib/wefunder_generated/models/audit_event.rb +234 -0
  56. data/lib/wefunder_generated/models/audit_event_attributes.rb +391 -0
  57. data/lib/wefunder_generated/models/audit_event_list_envelope.rb +225 -0
  58. data/lib/wefunder_generated/models/bulk_invite_link_create_input.rb +243 -0
  59. data/lib/wefunder_generated/models/bulk_invite_link_envelope.rb +237 -0
  60. data/lib/wefunder_generated/models/bulk_invite_link_envelope_errors_inner.rb +233 -0
  61. data/lib/wefunder_generated/models/bulk_invite_link_envelope_meta.rb +232 -0
  62. data/lib/wefunder_generated/models/campaign.rb +232 -0
  63. data/lib/wefunder_generated/models/campaign_attributes.rb +301 -0
  64. data/lib/wefunder_generated/models/campaign_list_envelope.rb +225 -0
  65. data/lib/wefunder_generated/models/comment_proposal_envelope.rb +274 -0
  66. data/lib/wefunder_generated/models/comment_proposal_envelope_draft.rb +260 -0
  67. data/lib/wefunder_generated/models/comment_proposal_envelope_proposal.rb +286 -0
  68. data/lib/wefunder_generated/models/comment_proposal_envelope_related_questions_inner.rb +264 -0
  69. data/lib/wefunder_generated/models/company.rb +234 -0
  70. data/lib/wefunder_generated/models/company_attributes.rb +318 -0
  71. data/lib/wefunder_generated/models/company_attributes_location.rb +235 -0
  72. data/lib/wefunder_generated/models/company_disclosures.rb +268 -0
  73. data/lib/wefunder_generated/models/company_disclosures_attributes.rb +434 -0
  74. data/lib/wefunder_generated/models/company_disclosures_attributes_business.rb +255 -0
  75. data/lib/wefunder_generated/models/company_disclosures_attributes_capital_structure_inner.rb +255 -0
  76. data/lib/wefunder_generated/models/company_disclosures_attributes_current_position.rb +281 -0
  77. data/lib/wefunder_generated/models/company_disclosures_attributes_filing.rb +256 -0
  78. data/lib/wefunder_generated/models/company_disclosures_attributes_financial_statements.rb +233 -0
  79. data/lib/wefunder_generated/models/company_disclosures_attributes_outstanding_debts.rb +227 -0
  80. data/lib/wefunder_generated/models/company_disclosures_attributes_outstanding_debts_items_inner.rb +255 -0
  81. data/lib/wefunder_generated/models/company_disclosures_attributes_outstanding_notes_inner.rb +284 -0
  82. data/lib/wefunder_generated/models/company_disclosures_attributes_prior_offerings_inner.rb +266 -0
  83. data/lib/wefunder_generated/models/company_disclosures_attributes_ratios.rb +272 -0
  84. data/lib/wefunder_generated/models/company_disclosures_attributes_related_parties.rb +226 -0
  85. data/lib/wefunder_generated/models/company_disclosures_attributes_related_parties_parties_inner.rb +265 -0
  86. data/lib/wefunder_generated/models/company_disclosures_attributes_use_of_funds_inner.rb +226 -0
  87. data/lib/wefunder_generated/models/company_disclosures_attributes_voting_power_inner.rb +236 -0
  88. data/lib/wefunder_generated/models/company_disclosures_envelope.rb +214 -0
  89. data/lib/wefunder_generated/models/company_envelope.rb +214 -0
  90. data/lib/wefunder_generated/models/company_pitch.rb +268 -0
  91. data/lib/wefunder_generated/models/company_pitch_attributes.rb +311 -0
  92. data/lib/wefunder_generated/models/company_pitch_attributes_perks.rb +248 -0
  93. data/lib/wefunder_generated/models/company_pitch_attributes_story.rb +255 -0
  94. data/lib/wefunder_generated/models/company_pitch_envelope.rb +214 -0
  95. data/lib/wefunder_generated/models/company_question.rb +268 -0
  96. data/lib/wefunder_generated/models/company_question_attributes.rb +329 -0
  97. data/lib/wefunder_generated/models/company_question_attributes_answers_inner.rb +253 -0
  98. data/lib/wefunder_generated/models/company_question_list_envelope.rb +225 -0
  99. data/lib/wefunder_generated/models/company_question_list_envelope_meta.rb +301 -0
  100. data/lib/wefunder_generated/models/company_question_person.rb +247 -0
  101. data/lib/wefunder_generated/models/company_search_result.rb +268 -0
  102. data/lib/wefunder_generated/models/company_search_result_attributes.rb +266 -0
  103. data/lib/wefunder_generated/models/company_search_result_list_envelope.rb +225 -0
  104. data/lib/wefunder_generated/models/company_search_result_list_envelope_meta.rb +223 -0
  105. data/lib/wefunder_generated/models/company_totals.rb +249 -0
  106. data/lib/wefunder_generated/models/company_update.rb +269 -0
  107. data/lib/wefunder_generated/models/company_update_attributes.rb +350 -0
  108. data/lib/wefunder_generated/models/company_update_attributes_author.rb +215 -0
  109. data/lib/wefunder_generated/models/company_update_envelope.rb +214 -0
  110. data/lib/wefunder_generated/models/company_update_list_envelope.rb +225 -0
  111. data/lib/wefunder_generated/models/company_update_list_envelope_meta.rb +243 -0
  112. data/lib/wefunder_generated/models/connected_app.rb +233 -0
  113. data/lib/wefunder_generated/models/connected_app_attributes.rb +258 -0
  114. data/lib/wefunder_generated/models/connected_app_list_envelope.rb +216 -0
  115. data/lib/wefunder_generated/models/connected_app_revocation.rb +223 -0
  116. data/lib/wefunder_generated/models/connected_app_revocation_envelope.rb +214 -0
  117. data/lib/wefunder_generated/models/convertible_note_security.rb +266 -0
  118. data/lib/wefunder_generated/models/convertible_note_security_terms.rb +266 -0
  119. data/lib/wefunder_generated/models/create_installation_request.rb +294 -0
  120. data/lib/wefunder_generated/models/create_installation_token_request.rb +216 -0
  121. data/lib/wefunder_generated/models/create_intent_request.rb +320 -0
  122. data/lib/wefunder_generated/models/create_partner_invite_request.rb +287 -0
  123. data/lib/wefunder_generated/models/create_webhook_endpoint_request.rb +291 -0
  124. data/lib/wefunder_generated/models/create_webhook_subscription_request.rb +263 -0
  125. data/lib/wefunder_generated/models/current_raise.rb +288 -0
  126. data/lib/wefunder_generated/models/deal_investor.rb +234 -0
  127. data/lib/wefunder_generated/models/deal_investor_attributes.rb +321 -0
  128. data/lib/wefunder_generated/models/deal_investor_list_envelope.rb +225 -0
  129. data/lib/wefunder_generated/models/deal_investor_list_envelope_meta.rb +252 -0
  130. data/lib/wefunder_generated/models/debt_security.rb +266 -0
  131. data/lib/wefunder_generated/models/debt_security_terms.rb +235 -0
  132. data/lib/wefunder_generated/models/delete_webhook_endpoint200_response.rb +214 -0
  133. data/lib/wefunder_generated/models/delete_webhook_endpoint200_response_data.rb +232 -0
  134. data/lib/wefunder_generated/models/disclosure_document.rb +224 -0
  135. data/lib/wefunder_generated/models/disclosure_person.rb +254 -0
  136. data/lib/wefunder_generated/models/eligible_target_list_envelope.rb +225 -0
  137. data/lib/wefunder_generated/models/eligible_target_list_envelope_data_inner.rb +295 -0
  138. data/lib/wefunder_generated/models/equity_security.rb +266 -0
  139. data/lib/wefunder_generated/models/equity_security_terms.rb +271 -0
  140. data/lib/wefunder_generated/models/error.rb +214 -0
  141. data/lib/wefunder_generated/models/error_error.rb +254 -0
  142. data/lib/wefunder_generated/models/exemption.rb +271 -0
  143. data/lib/wefunder_generated/models/fiscal_year_financials.rb +296 -0
  144. data/lib/wefunder_generated/models/follow_state_envelope.rb +214 -0
  145. data/lib/wefunder_generated/models/follow_state_envelope_data.rb +268 -0
  146. data/lib/wefunder_generated/models/follow_state_envelope_data_attributes.rb +225 -0
  147. data/lib/wefunder_generated/models/followed_company.rb +268 -0
  148. data/lib/wefunder_generated/models/followed_company_attributes.rb +275 -0
  149. data/lib/wefunder_generated/models/followed_company_list_envelope.rb +225 -0
  150. data/lib/wefunder_generated/models/full_attribution.rb +272 -0
  151. data/lib/wefunder_generated/models/full_attribution_attribution.rb +326 -0
  152. data/lib/wefunder_generated/models/full_attribution_investor.rb +242 -0
  153. data/lib/wefunder_generated/models/full_attribution_status.rb +257 -0
  154. data/lib/wefunder_generated/models/fund_security.rb +267 -0
  155. data/lib/wefunder_generated/models/installation.rb +233 -0
  156. data/lib/wefunder_generated/models/installation_attributes.rb +307 -0
  157. data/lib/wefunder_generated/models/installation_attributes_target.rb +267 -0
  158. data/lib/wefunder_generated/models/installation_envelope.rb +223 -0
  159. data/lib/wefunder_generated/models/installation_list_envelope.rb +225 -0
  160. data/lib/wefunder_generated/models/installation_list_envelope_meta.rb +214 -0
  161. data/lib/wefunder_generated/models/installation_token_envelope.rb +232 -0
  162. data/lib/wefunder_generated/models/installation_token_envelope_token.rb +252 -0
  163. data/lib/wefunder_generated/models/intent.rb +234 -0
  164. data/lib/wefunder_generated/models/intent_attributes.rb +375 -0
  165. data/lib/wefunder_generated/models/intent_attributes_resource_id.rb +106 -0
  166. data/lib/wefunder_generated/models/intent_envelope.rb +214 -0
  167. data/lib/wefunder_generated/models/intent_list_envelope.rb +225 -0
  168. data/lib/wefunder_generated/models/intent_preview_envelope.rb +214 -0
  169. data/lib/wefunder_generated/models/intent_preview_envelope_data.rb +257 -0
  170. data/lib/wefunder_generated/models/intent_preview_envelope_data_attributes.rb +262 -0
  171. data/lib/wefunder_generated/models/intent_review.rb +234 -0
  172. data/lib/wefunder_generated/models/investment_change_list_envelope.rb +225 -0
  173. data/lib/wefunder_generated/models/investment_change_list_envelope_meta.rb +285 -0
  174. data/lib/wefunder_generated/models/investment_delta_record.rb +504 -0
  175. data/lib/wefunder_generated/models/investment_delta_record_amounts.rb +251 -0
  176. data/lib/wefunder_generated/models/investment_delta_record_blockers_inner.rb +224 -0
  177. data/lib/wefunder_generated/models/investment_delta_record_contracts_inner.rb +234 -0
  178. data/lib/wefunder_generated/models/investment_delta_record_investor.rb +286 -0
  179. data/lib/wefunder_generated/models/investment_envelope.rb +223 -0
  180. data/lib/wefunder_generated/models/investment_envelope_meta.rb +270 -0
  181. data/lib/wefunder_generated/models/investment_list_envelope.rb +225 -0
  182. data/lib/wefunder_generated/models/investment_list_envelope_meta.rb +288 -0
  183. data/lib/wefunder_generated/models/investment_session.rb +232 -0
  184. data/lib/wefunder_generated/models/investment_session_attributes.rb +384 -0
  185. data/lib/wefunder_generated/models/investment_session_attributes_events_inner.rb +232 -0
  186. data/lib/wefunder_generated/models/investment_session_create_input.rb +331 -0
  187. data/lib/wefunder_generated/models/investment_session_envelope.rb +223 -0
  188. data/lib/wefunder_generated/models/investment_session_list_envelope.rb +225 -0
  189. data/lib/wefunder_generated/models/investment_session_list_envelope_meta.rb +242 -0
  190. data/lib/wefunder_generated/models/investor.rb +233 -0
  191. data/lib/wefunder_generated/models/investor_attributes.rb +262 -0
  192. data/lib/wefunder_generated/models/investor_envelope.rb +214 -0
  193. data/lib/wefunder_generated/models/investor_list_envelope.rb +225 -0
  194. data/lib/wefunder_generated/models/invite_link.rb +233 -0
  195. data/lib/wefunder_generated/models/invite_link_attributes.rb +375 -0
  196. data/lib/wefunder_generated/models/invite_link_attributes_events_inner.rb +243 -0
  197. data/lib/wefunder_generated/models/invite_link_create_input.rb +295 -0
  198. data/lib/wefunder_generated/models/invite_link_envelope.rb +223 -0
  199. data/lib/wefunder_generated/models/invite_link_list_envelope.rb +225 -0
  200. data/lib/wefunder_generated/models/invite_link_update_input.rb +227 -0
  201. data/lib/wefunder_generated/models/invite_syndicate_member_request.rb +277 -0
  202. data/lib/wefunder_generated/models/marketing_partner.rb +295 -0
  203. data/lib/wefunder_generated/models/marketing_partner_envelope.rb +214 -0
  204. data/lib/wefunder_generated/models/member_investment.rb +234 -0
  205. data/lib/wefunder_generated/models/member_investment_attributes.rb +301 -0
  206. data/lib/wefunder_generated/models/member_investment_list_envelope.rb +225 -0
  207. data/lib/wefunder_generated/models/member_investment_list_envelope_meta.rb +225 -0
  208. data/lib/wefunder_generated/models/my_company.rb +268 -0
  209. data/lib/wefunder_generated/models/my_company_attributes.rb +257 -0
  210. data/lib/wefunder_generated/models/my_company_list_envelope.rb +225 -0
  211. data/lib/wefunder_generated/models/offering.rb +267 -0
  212. data/lib/wefunder_generated/models/offering_attributes.rb +469 -0
  213. data/lib/wefunder_generated/models/offering_attributes_intended_security.rb +258 -0
  214. data/lib/wefunder_generated/models/offering_attributes_security.rb +105 -0
  215. data/lib/wefunder_generated/models/offering_envelope.rb +214 -0
  216. data/lib/wefunder_generated/models/offering_list_envelope.rb +225 -0
  217. data/lib/wefunder_generated/models/offering_list_envelope_meta.rb +265 -0
  218. data/lib/wefunder_generated/models/offering_stats_envelope.rb +223 -0
  219. data/lib/wefunder_generated/models/offering_stats_envelope_data.rb +253 -0
  220. data/lib/wefunder_generated/models/offering_stats_envelope_data_by_status_value.rb +232 -0
  221. data/lib/wefunder_generated/models/offering_stats_envelope_data_total.rb +233 -0
  222. data/lib/wefunder_generated/models/offering_stats_envelope_meta.rb +258 -0
  223. data/lib/wefunder_generated/models/offering_warnings_inner.rb +257 -0
  224. data/lib/wefunder_generated/models/other_security.rb +267 -0
  225. data/lib/wefunder_generated/models/other_security_terms.rb +235 -0
  226. data/lib/wefunder_generated/models/pagination_meta.rb +234 -0
  227. data/lib/wefunder_generated/models/pagination_meta_next_cursor.rb +106 -0
  228. data/lib/wefunder_generated/models/partner_investment.rb +232 -0
  229. data/lib/wefunder_generated/models/partner_investment_attributes.rb +305 -0
  230. data/lib/wefunder_generated/models/partner_investment_attributes_accreditation.rb +289 -0
  231. data/lib/wefunder_generated/models/partner_investment_envelope.rb +214 -0
  232. data/lib/wefunder_generated/models/partner_investment_list_envelope.rb +225 -0
  233. data/lib/wefunder_generated/models/partner_investor.rb +233 -0
  234. data/lib/wefunder_generated/models/partner_investor_attributes.rb +264 -0
  235. data/lib/wefunder_generated/models/partner_investor_list_envelope.rb +225 -0
  236. data/lib/wefunder_generated/models/partner_invite.rb +360 -0
  237. data/lib/wefunder_generated/models/partner_invite_envelope.rb +214 -0
  238. data/lib/wefunder_generated/models/partner_invite_list_envelope.rb +216 -0
  239. data/lib/wefunder_generated/models/partner_webhook_event.rb +257 -0
  240. data/lib/wefunder_generated/models/partner_webhook_event_envelope.rb +214 -0
  241. data/lib/wefunder_generated/models/partner_webhook_subscription.rb +232 -0
  242. data/lib/wefunder_generated/models/partner_webhook_subscription_attributes.rb +243 -0
  243. data/lib/wefunder_generated/models/partner_webhook_subscription_create_input.rb +239 -0
  244. data/lib/wefunder_generated/models/partner_webhook_subscription_envelope.rb +214 -0
  245. data/lib/wefunder_generated/models/partner_webhook_subscription_list_envelope.rb +216 -0
  246. data/lib/wefunder_generated/models/partner_webhook_subscription_with_secret.rb +239 -0
  247. data/lib/wefunder_generated/models/partner_webhook_subscription_with_secret_all_of_attributes.rb +215 -0
  248. data/lib/wefunder_generated/models/partner_webhook_subscription_with_secret_envelope.rb +214 -0
  249. data/lib/wefunder_generated/models/past_round.rb +309 -0
  250. data/lib/wefunder_generated/models/perk_tier.rb +226 -0
  251. data/lib/wefunder_generated/models/pitch_block.rb +375 -0
  252. data/lib/wefunder_generated/models/pitch_block_links_inner.rb +226 -0
  253. data/lib/wefunder_generated/models/portfolio_company_ref.rb +235 -0
  254. data/lib/wefunder_generated/models/portfolio_holding.rb +258 -0
  255. data/lib/wefunder_generated/models/portfolio_position.rb +268 -0
  256. data/lib/wefunder_generated/models/portfolio_position_attributes.rb +456 -0
  257. data/lib/wefunder_generated/models/portfolio_position_list_envelope.rb +225 -0
  258. data/lib/wefunder_generated/models/portfolio_position_list_envelope_meta.rb +234 -0
  259. data/lib/wefunder_generated/models/portfolio_security.rb +323 -0
  260. data/lib/wefunder_generated/models/portfolio_security_owner.rb +258 -0
  261. data/lib/wefunder_generated/models/portfolio_security_terms.rb +257 -0
  262. data/lib/wefunder_generated/models/portfolio_summary_envelope.rb +214 -0
  263. data/lib/wefunder_generated/models/portfolio_summary_envelope_data.rb +223 -0
  264. data/lib/wefunder_generated/models/portfolio_summary_envelope_data_attributes.rb +292 -0
  265. data/lib/wefunder_generated/models/portfolio_summary_envelope_data_attributes_position_counts.rb +242 -0
  266. data/lib/wefunder_generated/models/preview_intent_request.rb +262 -0
  267. data/lib/wefunder_generated/models/register_as_partner_request.rb +232 -0
  268. data/lib/wefunder_generated/models/reorder_syndicate_members_request.rb +224 -0
  269. data/lib/wefunder_generated/models/revenue_share_security.rb +266 -0
  270. data/lib/wefunder_generated/models/revenue_share_security_terms.rb +300 -0
  271. data/lib/wefunder_generated/models/safe_security.rb +266 -0
  272. data/lib/wefunder_generated/models/safe_security_terms.rb +257 -0
  273. data/lib/wefunder_generated/models/security.rb +66 -0
  274. data/lib/wefunder_generated/models/security_summary.rb +272 -0
  275. data/lib/wefunder_generated/models/spv.rb +232 -0
  276. data/lib/wefunder_generated/models/spv_attributes.rb +353 -0
  277. data/lib/wefunder_generated/models/spv_attributes_target_company.rb +223 -0
  278. data/lib/wefunder_generated/models/spv_cancel_intent_envelope.rb +224 -0
  279. data/lib/wefunder_generated/models/spv_cancel_intent_envelope_meta.rb +214 -0
  280. data/lib/wefunder_generated/models/spv_close_intent_envelope.rb +224 -0
  281. data/lib/wefunder_generated/models/spv_close_intent_envelope_meta.rb +214 -0
  282. data/lib/wefunder_generated/models/spv_create_input.rb +274 -0
  283. data/lib/wefunder_generated/models/spv_envelope.rb +214 -0
  284. data/lib/wefunder_generated/models/spv_list_envelope.rb +225 -0
  285. data/lib/wefunder_generated/models/spv_metrics.rb +261 -0
  286. data/lib/wefunder_generated/models/spv_settings.rb +275 -0
  287. data/lib/wefunder_generated/models/spv_status.rb +232 -0
  288. data/lib/wefunder_generated/models/spv_status_attributes.rb +289 -0
  289. data/lib/wefunder_generated/models/spv_status_envelope.rb +223 -0
  290. data/lib/wefunder_generated/models/spv_terms.rb +348 -0
  291. data/lib/wefunder_generated/models/spv_with_meta_envelope.rb +223 -0
  292. data/lib/wefunder_generated/models/spv_with_meta_envelope_meta.rb +214 -0
  293. data/lib/wefunder_generated/models/syndicate.rb +234 -0
  294. data/lib/wefunder_generated/models/syndicate_attributes.rb +412 -0
  295. data/lib/wefunder_generated/models/syndicate_attributes_annual_subscription.rb +236 -0
  296. data/lib/wefunder_generated/models/syndicate_attributes_traditional_fund.rb +225 -0
  297. data/lib/wefunder_generated/models/syndicate_deal.rb +234 -0
  298. data/lib/wefunder_generated/models/syndicate_deal_attributes.rb +435 -0
  299. data/lib/wefunder_generated/models/syndicate_deal_attributes_exemption.rb +105 -0
  300. data/lib/wefunder_generated/models/syndicate_deal_attributes_security.rb +105 -0
  301. data/lib/wefunder_generated/models/syndicate_deal_close_intent_envelope.rb +224 -0
  302. data/lib/wefunder_generated/models/syndicate_deal_close_intent_envelope_meta.rb +214 -0
  303. data/lib/wefunder_generated/models/syndicate_deal_detail_envelope.rb +214 -0
  304. data/lib/wefunder_generated/models/syndicate_deal_envelope.rb +214 -0
  305. data/lib/wefunder_generated/models/syndicate_deal_finalize_intent_envelope.rb +224 -0
  306. data/lib/wefunder_generated/models/syndicate_deal_finalize_intent_envelope_meta.rb +214 -0
  307. data/lib/wefunder_generated/models/syndicate_deal_list_envelope.rb +225 -0
  308. data/lib/wefunder_generated/models/syndicate_detail_envelope.rb +214 -0
  309. data/lib/wefunder_generated/models/syndicate_list_envelope.rb +225 -0
  310. data/lib/wefunder_generated/models/syndicate_member.rb +234 -0
  311. data/lib/wefunder_generated/models/syndicate_member_attributes.rb +650 -0
  312. data/lib/wefunder_generated/models/syndicate_member_envelope.rb +214 -0
  313. data/lib/wefunder_generated/models/syndicate_member_list_envelope.rb +225 -0
  314. data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope.rb +214 -0
  315. data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope_data.rb +223 -0
  316. data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope_data_attributes.rb +302 -0
  317. data/lib/wefunder_generated/models/syndicate_portfolio_summary_envelope_data_attributes_deal_counts.rb +242 -0
  318. data/lib/wefunder_generated/models/syndicate_statistics.rb +224 -0
  319. data/lib/wefunder_generated/models/syndicate_statistics_attributes.rb +277 -0
  320. data/lib/wefunder_generated/models/syndicate_statistics_envelope.rb +214 -0
  321. data/lib/wefunder_generated/models/tag_ref.rb +238 -0
  322. data/lib/wefunder_generated/models/target_company_input.rb +258 -0
  323. data/lib/wefunder_generated/models/test_webhook_endpoint_request.rb +249 -0
  324. data/lib/wefunder_generated/models/update_syndicate_member_request.rb +221 -0
  325. data/lib/wefunder_generated/models/update_syndicate_member_request_member.rb +251 -0
  326. data/lib/wefunder_generated/models/update_syndicate_request.rb +221 -0
  327. data/lib/wefunder_generated/models/update_syndicate_request_syndicate.rb +232 -0
  328. data/lib/wefunder_generated/models/update_webhook_endpoint_request.rb +252 -0
  329. data/lib/wefunder_generated/models/user.rb +233 -0
  330. data/lib/wefunder_generated/models/user_attributes.rb +300 -0
  331. data/lib/wefunder_generated/models/user_envelope.rb +214 -0
  332. data/lib/wefunder_generated/models/validation_error.rb +215 -0
  333. data/lib/wefunder_generated/models/validation_error_error.rb +234 -0
  334. data/lib/wefunder_generated/models/validation_error_error_details_inner.rb +232 -0
  335. data/lib/wefunder_generated/models/webhook_delivery.rb +318 -0
  336. data/lib/wefunder_generated/models/webhook_endpoint.rb +232 -0
  337. data/lib/wefunder_generated/models/webhook_endpoint_attributes.rb +327 -0
  338. data/lib/wefunder_generated/models/webhook_endpoint_envelope.rb +223 -0
  339. data/lib/wefunder_generated/models/webhook_endpoint_envelope_meta.rb +214 -0
  340. data/lib/wefunder_generated/models/webhook_endpoint_list_envelope.rb +225 -0
  341. data/lib/wefunder_generated/models/webhook_endpoint_list_envelope_meta.rb +233 -0
  342. data/lib/wefunder_generated/models/webhook_endpoint_test_result_envelope.rb +223 -0
  343. data/lib/wefunder_generated/models/webhook_endpoint_test_result_envelope_data.rb +306 -0
  344. data/lib/wefunder_generated/models/webhook_subscription.rb +304 -0
  345. data/lib/wefunder_generated/models/webhook_subscription_envelope.rb +214 -0
  346. data/lib/wefunder_generated/models/webhook_subscription_list_envelope.rb +225 -0
  347. data/lib/wefunder_generated/models/webhook_subscription_list_envelope_meta.rb +225 -0
  348. data/lib/wefunder_generated/models/webhook_subscription_with_recent_deliveries_envelope.rb +214 -0
  349. data/lib/wefunder_generated/models/webhook_subscription_with_recent_deliveries_envelope_data.rb +322 -0
  350. data/lib/wefunder_generated/models/webhook_subscription_with_secret2_envelope.rb +214 -0
  351. data/lib/wefunder_generated/models/webhook_subscription_with_secret2_envelope_data.rb +320 -0
  352. data/lib/wefunder_generated/models/webhook_subscription_with_secret_envelope.rb +214 -0
  353. data/lib/wefunder_generated/models/webhook_subscription_with_secret_envelope_data.rb +320 -0
  354. data/lib/wefunder_generated/models/webhook_test_result.rb +233 -0
  355. data/lib/wefunder_generated/models/webhook_test_result_envelope.rb +214 -0
  356. data/lib/wefunder_generated/models/wefunder_round.rb +330 -0
  357. data/lib/wefunder_generated/models/wefunder_round_exemption.rb +104 -0
  358. data/lib/wefunder_generated/models/wefunder_round_security.rb +104 -0
  359. data/lib/wefunder_generated/version.rb +15 -0
  360. data/lib/wefunder_generated.rb +382 -0
  361. 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