# GENERATED from swagger.yaml (public tier: ga+beta) — do not edit, do not commit. openapi: 3.0.1 info: title: Wefunder API v2 description: | OAuth 2.0 API for accessing Wefunder data programmatically. ## Authentication This API uses OAuth 2.0 for authentication. To get started: 1. Create an OAuth application at `/oauth/applications` 2. Get your Client ID and Client Secret 3. Implement the OAuth 2.0 Authorization Code flow 4. Use the access token to make API requests ## Rate Limits - Standard API: 1,000 requests/hour per token - Admin API: 10,000 requests/hour per token ## Base URL All API requests should be made to: `https://api.wefunder.com/api/v2` version: '2.0' contact: name: Wefunder API Support email: api@wefunder.com servers: - url: https://{environment}.wefunder.com/api/v2 variables: environment: default: api enum: - api - staging paths: /explore: get: tags: - Explore summary: List public offerings description: | Returns the offerings shown on wefunder.com/explore — the curated set of companies that meet Wefunder's discoverability bar (a soft-confirmed traction threshold plus approval), **one offering per company**. An "offering" is a fundraise, addressed by its `ofr_` external id. This is a public, read-only endpoint backed by the `read:public` scope. It is reachable with either a server-side (client_credentials) token or a user access token carrying `read:public`. Private rounds (e.g. Reg D 506(b)) are never listed, and low-traction or unapproved companies are excluded — exactly as they are on the website. Order with `sort` (default: most raised); paginate with `cursor`. `meta.total_count` is the number of offerings across all pages. Use this endpoint to: - Discover and monitor active deals - Build deal aggregators and public listings operationId: listOfferings security: - bearerAuth: - read:public parameters: - $ref: '#/components/parameters/cursor' - name: sort in: query description: | Result ordering: - `most_raised` (default) — highest amount raised first - `closing_soon` — soonest funding close first - `newest` — most recently launched first - `most_investors` — highest investor count first schema: type: string enum: - most_raised - closing_soon - newest - most_investors default: most_raised example: most_raised responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/OfferingListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimitExceeded' /offerings/{external_id}: get: tags: - Explore summary: Get a public offering description: | Retrieves a single public offering by its `ofr_` external id. A valid id for a non-public offering (e.g. a private Reg D 506(b) round) returns `404` — having an id does not make an offering publicly resolvable. operationId: getOffering security: - bearerAuth: - read:public parameters: - name: external_id in: path required: true description: The offering's `ofr_` external id. schema: type: string example: ofr_yw3OhusvpfJP4Pk3wRYV1F2R responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/OfferingEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimitExceeded' /users/me: get: tags: - Users summary: Get current user profile description: | Retrieves the profile information for the currently authenticated user. This endpoint returns basic user details including name, email, and account status. Use this endpoint to: - Display user information in your application - Verify the identity of the authenticated user - Check user account status and permissions The response includes the user's unique ID which can be used to reference the user in other API calls. operationId: getCurrentUser security: - bearerAuth: - read:profile responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/UserEnvelope' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimitExceeded' /investments: get: tags: - Investments summary: List user investments description: | Retrieves a paginated list of all investments made by the authenticated user. Each investment includes details about the amount invested, the company, investment date, and current status. Use this endpoint to: - Display a user's investment portfolio - Track investment history and performance - Build custom reporting and analytics The list is ordered by investment date (newest first) and supports cursor-based pagination for efficient iteration through large portfolios. Each investment object includes the campaign details and current valuation information. operationId: listInvestments security: - bearerAuth: - read:investments parameters: - $ref: '#/components/parameters/cursor' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/InvestmentListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimitExceeded' /campaigns: get: tags: - Campaigns summary: List founder campaigns description: | Retrieves all fundraising campaigns for companies where the authenticated user is a founder. This endpoint returns comprehensive campaign data including funding progress, investor counts, and campaign status. Use this endpoint to: - Build custom founder dashboards - Monitor fundraising progress across multiple campaigns - Generate reports on campaign performance - Integrate campaign data into your own tools Only founders with the appropriate permissions can access this endpoint. The response includes both active and closed campaigns, with detailed metrics for each. operationId: listCampaigns security: - bearerAuth: - read:offerings parameters: - $ref: '#/components/parameters/cursor' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CampaignListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/RateLimitExceeded' /syndicates: get: tags: - Syndicates summary: List syndicates description: | Returns syndicates the authenticated user can manage, based on their roles. Results can be filtered by status and sorted by name or creation date. operationId: listSyndicates security: - bearerAuth: - read:syndicates parameters: - name: cursor in: query schema: type: string description: Pagination cursor from previous response - name: limit in: query schema: type: integer default: 25 maximum: 100 description: Results per page responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicateListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimitExceeded' /syndicates/{syndicate_id}: get: tags: - Syndicates summary: Get syndicate details description: Returns full details for a specific syndicate including settings, team configuration, and summary metrics. operationId: getSyndicate security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicateDetailEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Syndicate not found content: application/json: schema: $ref: '#/components/schemas/Error' patch: tags: - Syndicates summary: Update syndicate settings description: | Update syndicate configuration. Currently supports name, description, and tagline. Requires `write:syndicates` scope and at least operator permission. operationId: updateSyndicate security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' requestBody: required: true content: application/json: schema: type: object required: - syndicate properties: syndicate: type: object properties: name: type: string example: Acme Syndicate description: type: string tagline: type: string responses: '200': description: Syndicate updated content: application/json: schema: $ref: '#/components/schemas/SyndicateDetailEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/members: get: tags: - Syndicate Members summary: List members description: | Returns the member directory for a syndicate with role, status, tags, sidebar notes, and investment history summaries. Supports filtering, sorting, search, and pagination. operationId: listSyndicateMembers security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - name: role in: query schema: type: string description: Filter by role (e.g. creator, manager, member, invitee, applicant) - name: permission in: query schema: type: string enum: - full_access - operator description: Filter by syndicate permission level - name: search in: query schema: type: string description: Search by name or email - name: sort in: query schema: type: string enum: - relevance - newest - oldest - alphabetical - last_activity description: Sort order for results - name: has_invested in: query schema: type: string enum: - 'true' - 'false' description: Filter by whether the member has invested in any syndicate deal - name: accredited in: query schema: type: string enum: - 'true' description: Filter to accredited members only - name: tag in: query schema: type: string description: Filter by 'can help with' tag (ILIKE match) responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/invite: post: tags: - Syndicate Members summary: Invite member description: Send an invitation to join the syndicate by email. operationId: inviteSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' requestBody: required: true content: application/json: schema: type: object required: - email properties: email: type: string format: email example: jane@example.com name: type: string description: Display name for the invitee syndicate_permission: type: string enum: - full_access - operator default: operator description: Permission level. Only the syndicate creator can assign full_access (admin). responses: '201': description: Invitation sent content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: Validation error (e.g. already a member) content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/members/{member_id}: patch: tags: - Syndicate Members summary: Update member description: Update title and carry override for a member. Requires operator permission. operationId: updateSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' requestBody: required: true content: application/json: schema: type: object required: - member properties: member: type: object properties: title: type: string example: Lead Investor carry_percentage_override: type: string example: '20.0' responses: '200': description: Member updated content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' delete: tags: - Syndicate Members summary: Remove member (requires intent) description: | Removing a member is irreversible and requires human approval through the Intent system. This endpoint always returns 422 with a `use_intents` error directing you to `POST /v2/intents` with action `syndicates.remove_member`. operationId: removeSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '422': description: Use intents for this action content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/members/{member_id}/approve: post: tags: - Syndicate Members summary: Approve applicant description: Approve a pending applicant. Only applicants can be approved via API; invitees must accept their invite link. operationId: approveSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Member approved content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/{member_id}/hide: post: tags: - Syndicate Members summary: Exile member description: Exile (hide) a member from the syndicate. Sets their role to exiled. operationId: hideSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Member hidden content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/{member_id}/promote: post: tags: - Syndicate Members summary: Promote member to admin description: Promote a member to full_access (admin) role. Only the syndicate creator can promote members. operationId: promoteSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Member promoted content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/{member_id}/demote: post: tags: - Syndicate Members summary: Demote member to operator description: Demote a member from full_access (admin) to operator permission. operationId: demoteSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Member demoted content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/{member_id}/restore: post: tags: - Syndicate Members summary: Restore exiled or resigned member description: Restore a member who was previously exiled or resigned. Sets their role back to member. operationId: restoreSyndicateMember security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Member restored content: application/json: schema: $ref: '#/components/schemas/SyndicateMemberEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/{member_id}/resend_invite: post: tags: - Syndicate Members summary: Resend invitation operationId: resendSyndicateInvite security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Invitation resent '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/reorder: post: tags: - Syndicate Members summary: Reorder members description: Set the display order for the member directory. operationId: reorderSyndicateMembers security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' requestBody: required: true content: application/json: schema: type: object required: - member_ids properties: member_ids: type: array items: type: string example: - cr_123 - cr_456 - cr_789 responses: '200': description: Order updated '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/members/export_csv: post: tags: - Syndicate Members summary: Export members CSV description: | Generate a CSV export of the member directory. Returns the CSV data directly with Content-Type text/csv. Requires operator permission. operationId: exportSyndicateMembersCsv security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' responses: '200': description: CSV data content: text/csv: schema: type: string '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/deals: get: tags: - Syndicate Deals summary: List deals description: Returns all deals (fundraises) within a syndicate with status, terms, and metrics. operationId: listSyndicateDeals security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicateDealListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /syndicates/{syndicate_id}/deals/{fundraise_id}: get: tags: - Syndicate Deals summary: Get deal details description: Returns full details for a specific deal including investor breakdown. operationId: getSyndicateDeal security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicateDealDetailEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Deal not found content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/deals/{fundraise_id}/investors: get: tags: - Syndicate Deals summary: List deal investors description: | Returns syndicate members who invested in this deal. Results are scoped to syndicate member user IDs only and sorted by amount descending. The `user_email` field is **moderator-only** — it returns null for non-moderator callers (i.e., users who are not a manager/operator of the syndicate). All monetary values are strings representing cents to avoid floating-point precision issues. operationId: listSyndicateDealInvestors security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' - name: cursor in: query schema: type: string description: Pagination cursor from previous response - name: limit in: query schema: type: integer default: 25 maximum: 100 description: Results per page responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/DealInvestorListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Deal not found content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/members/{member_id}/investments: get: tags: - Syndicate Members summary: List member investments description: | Returns this member's investments in syndicate deals. Scoped to the syndicate's selected deals (one per company, matching directory semantics). All monetary values are strings representing cents to avoid floating-point precision issues. operationId: listSyndicateMemberInvestments security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/member_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/MemberInvestmentListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Member not found content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/statistics: get: tags: - Syndicate Statistics summary: Get syndicate statistics description: | Returns aggregate metrics for a syndicate including member counts, deal counts, total raised, and recent activity. `total_raised` and `total_investors` are computed from directory-selected deals (one per company). `total_deals` and `live_deals` use all linked deals. All monetary values are strings representing cents to avoid floating-point precision issues. operationId: getSyndicateStatistics security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicateStatisticsEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Syndicate not found content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/deals/{fundraise_id}/close: post: tags: - Syndicate Deals summary: Close deal (requires intent) description: | Closing a deal is irreversible and requires human approval through the Intent system. This endpoint always returns 422 with a `use_intents` error directing you to `POST /v2/intents` with action `syndicates.close_deal`. operationId: closeSyndicateDeal security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' responses: '422': description: Use intents for this action content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/deals/{fundraise_id}/finalize: post: tags: - Syndicate Deals summary: Finalize deal (requires intent) description: | Finalizing a deal triggers disbursement and is irreversible. It requires human approval through the Intent system. This endpoint always returns 422 with a `use_intents` error directing you to `POST /v2/intents` with action `syndicates.finalize_deal`. operationId: finalizeSyndicateDeal security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' responses: '422': description: Use intents for this action content: application/json: schema: $ref: '#/components/schemas/Error' /intents: post: tags: - Intents summary: Propose an intent description: | Propose a dangerous or irreversible action for human approval. Returns a `review_url` where a human must review the impact and approve before the action executes. The entity that proposes an intent can never approve it. Approval always happens on Wefunder's UI by a user with permission on the resource. See [Intents documentation](/concepts/intents) for the full pattern. operationId: createIntent security: - bearerAuth: - write:syndicates requestBody: required: true content: application/json: schema: type: object required: - action_name - resource_type - resource_id properties: action_name: type: string description: The action to perform example: syndicates.close_deal enum: - syndicates.close_deal - syndicates.finalize_deal - syndicates.publish - syndicates.remove_member - syndicates.delete_draft resource_type: type: string example: Club description: The model class name of the resource resource_id: type: integer example: 42 params: type: object description: Action-specific parameters example: fundraise_id: 99 idempotency_key: type: string description: Client-provided deduplication key. If a pending/executed intent exists with this key, the existing intent is returned. example: close-acme-series-a requested_by_agent: type: string description: Human-readable name of the agent proposing the action example: Claude via MCP responses: '201': description: Intent created content: application/json: schema: $ref: '#/components/schemas/IntentEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: Duplicate idempotency key (returns existing intent) content: application/json: schema: $ref: '#/components/schemas/IntentEnvelope' '422': description: Invalid action or parameters content: application/json: schema: $ref: '#/components/schemas/Error' get: tags: - Intents summary: List intents description: List intents created by the current token, optionally filtered by status or resource. operationId: listIntents security: - bearerAuth: - read:syndicates parameters: - name: status in: query schema: type: string enum: - pending - approved - executed - expired - rejected - failed - name: resource_type in: query schema: type: string - name: resource_id in: query schema: type: integer - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer default: 25 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/IntentListEnvelope' '401': $ref: '#/components/responses/Unauthorized' /intents/{intent_id}: get: tags: - Intents summary: Get intent status description: | Check the current status of an intent. Use this to poll for approval after proposing an intent, or subscribe to `intent.*` webhook events instead. operationId: getIntent security: - bearerAuth: - read:syndicates parameters: - name: intent_id in: path required: true schema: type: string description: Intent UUID example: intent_abc123 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/IntentEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': description: Intent not found content: application/json: schema: $ref: '#/components/schemas/Error' /activity: get: tags: - Activity summary: List activity events description: | Returns actions taken by OAuth apps authorized by the current user. This is the API backing the user-facing activity feed at Settings > Activity. operationId: listActivity security: - bearerAuth: - read:profile parameters: - name: app_id in: query schema: type: integer description: Filter by connected app - name: action_pattern in: query schema: type: string description: Filter by action pattern (e.g. syndicates.* or syndicates.member.*) - name: resource_type in: query schema: type: string description: Filter by resource type (e.g. Club). Must be used with resource_id. - name: resource_id in: query schema: type: integer description: Filter by specific resource ID. Must be used with resource_type. - name: status in: query schema: type: string enum: - success - failure - denied - name: since in: query schema: type: string format: date-time - name: until in: query schema: type: string format: date-time - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer default: 25 maximum: 100 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/AuditEventListEnvelope' '401': $ref: '#/components/responses/Unauthorized' /connected-apps: get: tags: - Activity summary: List connected apps description: Returns all OAuth apps the user has authorized, with summary stats. operationId: listConnectedApps security: - bearerAuth: - read:profile responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/ConnectedAppListEnvelope' '401': $ref: '#/components/responses/Unauthorized' /connected-apps/{app_id}: delete: tags: - Activity summary: Revoke connected app description: | Revoke all tokens for this app. Any pending intents from this app are automatically expired. operationId: revokeConnectedApp security: - bearerAuth: - write:profile parameters: - name: app_id in: path required: true schema: type: integer responses: '200': description: App revoked, all tokens and pending intents expired content: application/json: schema: $ref: '#/components/schemas/ConnectedAppRevocationEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': description: App not found content: application/json: schema: $ref: '#/components/schemas/Error' /campaigns/{campaign_id}/attribution/stats: get: tags: - Attribution summary: Get aggregate attribution statistics description: | Returns aggregate attribution statistics for a campaign. This is a **Tier 0** endpoint available to all OAuth applications with the `read:attribution:aggregate` scope. Use this endpoint to: - Track overall marketing campaign performance - Measure conversion rates by UTM source/campaign - Monitor investment quality metrics - Build performance dashboards The response includes totals and breakdowns by UTM parameters. All counts exclude disputed attributions. **Privacy**: This endpoint returns aggregate data only. No individual investor information is exposed. operationId: getAttributionStats security: - bearerAuth: - read:attribution:aggregate parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/start_date' - $ref: '#/components/parameters/end_date' - $ref: '#/components/parameters/utm_source' - $ref: '#/components/parameters/utm_campaign' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/AttributionStatsEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/RateLimitExceeded' /campaigns/{campaign_id}/attribution/investments: get: tags: - Attribution summary: List attributed investments description: | Returns a list of attributed investments. The detail level depends on your access: **Tier 1 (Marketing Partners)** - Anonymized data: - Investor identity represented by opaque tokens (not real IDs) - Tokens are campaign-scoped (different token per campaign for same investor) - Investment amounts shown as tiers (small/medium/large), not exact values - No PII (email, name, address) is returned **Tier 2 (Founders/Employees)** - Full data with `detail_level=full`: - Real investment IDs (linkable to Wefunder admin) - Investor name, email, and username - Exact dollar amounts **Access**: Requires Tier 1+ access approval from Wefunder admin. operationId: listAttributedInvestments security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/cursor' - $ref: '#/components/parameters/start_date' - $ref: '#/components/parameters/end_date' - $ref: '#/components/parameters/utm_source' - $ref: '#/components/parameters/utm_campaign' - name: detail_level in: query description: | Level of detail to return: - `anonymized` (default): Opaque tokens, amount tiers - `full`: Real IDs, exact amounts, investor PII (founders only) Marketing partners always receive anonymized data regardless of this parameter. schema: type: string enum: - anonymized - full default: anonymized responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/AttributedInvestmentListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: | Access denied. This typically means: - Your OAuth application hasn't been approved for Tier 1 access - Your token doesn't include the `read:attribution:anonymized` scope To request Tier 1 access, visit https://wefunder.com/api/request-access content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/RateLimitExceeded' /campaigns/{campaign_id}/attribution/webhooks: get: tags: - Attribution Webhooks summary: List attribution webhook subscriptions description: | Returns all active webhook subscriptions for this campaign belonging to your OAuth application. **Tier 1** endpoint requiring admin-approved access with the `read:attribution:anonymized` scope. operationId: listWebhookSubscriptions security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/WebhookSubscriptionListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: tags: - Attribution Webhooks summary: Create webhook subscription description: | Creates a new webhook subscription for this campaign. When attributed investments change state, we'll POST a JSON payload to your target URL. **Tier 1** endpoint requiring admin-approved access. ## Webhook Payload Payloads include anonymized data (tokens, amount tiers) matching the `/attribution/investments` endpoint. ## Security - `target_url` must use HTTPS - `target_url` cannot resolve to private/internal IPs (SSRF protection) - Payloads are signed with HMAC-SHA256 using the returned `secret` ## Verification Verify webhook signatures using the `X-Wefunder-Signature` header: ``` timestamp = headers['X-Wefunder-Timestamp'] signature = headers['X-Wefunder-Signature'] expected = "sha256=" + HMAC-SHA256(secret, timestamp + "." + raw_body) ``` Reject requests where: - Signature doesn't match - Timestamp is more than 5 minutes old (replay protection) operationId: createWebhookSubscription security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' requestBody: required: true content: application/json: schema: type: object required: - target_url - events properties: target_url: type: string format: uri description: HTTPS URL to receive webhook POSTs example: https://yourapp.com/webhooks/wefunder events: type: array description: Events to subscribe to items: type: string enum: - investment.applied - investment.confirmed - investment.canceled example: - investment.applied - investment.confirmed responses: '201': description: Subscription created content: application/json: schema: $ref: '#/components/schemas/WebhookSubscriptionWithSecretEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: Validation error (invalid URL, events, or limit reached) content: application/json: schema: $ref: '#/components/schemas/Error' /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}: get: tags: - Attribution Webhooks summary: Get webhook subscription details description: | Returns details for a specific webhook subscription including recent delivery history. operationId: getWebhookSubscription security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/webhook_id' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/WebhookSubscriptionWithRecentDeliveriesEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Subscription not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: tags: - Attribution Webhooks summary: Delete webhook subscription description: | Deactivates a webhook subscription. The subscription will stop receiving events but historical delivery data is retained for 7 days. operationId: deleteWebhookSubscription security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/webhook_id' responses: '204': description: Subscription deleted '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Subscription not found content: application/json: schema: $ref: '#/components/schemas/Error' /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}/test: post: tags: - Attribution Webhooks summary: Send test webhook description: | Sends a test webhook to verify your endpoint is working correctly. The test payload includes a `"event": "test"` field to distinguish it from real events. operationId: testWebhookSubscription security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/webhook_id' responses: '200': description: Test webhook queued content: application/json: schema: $ref: '#/components/schemas/WebhookTestResultEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Subscription not found content: application/json: schema: $ref: '#/components/schemas/Error' '422': description: Subscription is inactive content: application/json: schema: $ref: '#/components/schemas/Error' /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}/regenerate_secret: post: tags: - Attribution Webhooks summary: Regenerate webhook signing secret description: | Generates a new HMAC signing secret for the webhook subscription. The old secret is immediately invalidated. **Important**: Store the new secret securely - it cannot be retrieved later. operationId: regenerateWebhookSecret security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/webhook_id' responses: '200': description: Secret regenerated content: application/json: schema: $ref: '#/components/schemas/WebhookSubscriptionWithSecret2Envelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Subscription not found content: application/json: schema: $ref: '#/components/schemas/Error' /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}/reactivate: post: tags: - Attribution Webhooks summary: Reactivate disabled webhook description: | Reactivates a webhook subscription that was auto-disabled after consecutive failures. Resets the failure counter to 0. Use this after fixing issues with your webhook endpoint. operationId: reactivateWebhookSubscription security: - bearerAuth: - read:attribution:anonymized parameters: - $ref: '#/components/parameters/campaign_id' - $ref: '#/components/parameters/webhook_id' responses: '200': description: Webhook reactivated content: application/json: schema: $ref: '#/components/schemas/WebhookSubscriptionEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Subscription not found content: application/json: schema: $ref: '#/components/schemas/Error' '422': description: Webhook is already active content: application/json: schema: $ref: '#/components/schemas/Error' /attribution/me: get: tags: - Attribution Partners summary: Get current user's attribution profile description: | Returns the authenticated user's marketing partner profile and connected campaigns. If the user is not registered as a marketing partner, returns partner info as null. operationId: getAttributionMe security: - bearerAuth: - read:attribution:aggregate responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/AttributionMeEnvelope' '401': $ref: '#/components/responses/Unauthorized' /attribution/campaigns: get: tags: - Attribution Partners summary: List campaigns accessible to the current user description: | Returns all campaigns the authenticated user can access for attribution data. For founders, this includes their own campaigns. For marketing partners, this includes campaigns they've been granted access to. operationId: listAttributionCampaigns security: - bearerAuth: - read:attribution:aggregate responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/AttributionCampaignSummaryListEnvelope' '401': $ref: '#/components/responses/Unauthorized' /attribution/partners/register: post: tags: - Attribution Partners summary: Register as a marketing partner description: | Registers the authenticated user as a marketing partner. After registration, the partner can create invites to request access to campaigns. operationId: registerAsPartner security: - bearerAuth: - read:attribution:aggregate requestBody: required: true content: application/json: schema: type: object required: - company_name properties: company_name: type: string description: Name of the marketing/agency company example: Acme Marketing Agency website: type: string format: uri description: Company website URL example: https://acme-marketing.com responses: '201': description: Partner registered content: application/json: schema: $ref: '#/components/schemas/MarketingPartnerEnvelope' '401': $ref: '#/components/responses/Unauthorized' '422': description: Already registered or validation error content: application/json: schema: $ref: '#/components/schemas/Error' /attribution/invites: get: tags: - Attribution Partners summary: List partner invites description: | Returns all invites created by or sent to the authenticated user. Includes both pending and accepted invites. operationId: listPartnerInvites security: - bearerAuth: - read:attribution:aggregate responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/PartnerInviteListEnvelope' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Attribution Partners summary: Create a partner invite description: | Creates an invite to establish a partner-company connection. **Directions**: - `partner_to_founder`: Marketing partner invites a founder to grant access - `founder_to_partner`: Founder invites a marketing partner to access their campaign For `partner_to_founder`: Creates a shareable invite link. For `founder_to_partner`: Sends an email to the partner. operationId: createPartnerInvite security: - bearerAuth: - read:attribution:aggregate requestBody: required: true content: application/json: schema: type: object required: - direction properties: direction: type: string enum: - partner_to_founder - founder_to_partner company_id: type: integer description: Required for founder_to_partner direction partner_email: type: string format: email description: Required for founder_to_partner direction access_level: type: integer default: 1 description: 1=anonymized (default), 2=detailed (future) responses: '201': description: Invite created content: application/json: schema: $ref: '#/components/schemas/PartnerInviteEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: Not authorized to create this invite content: application/json: schema: $ref: '#/components/schemas/Error' '422': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' /attribution/invites/{token}: delete: tags: - Attribution Partners summary: Revoke a partner invite description: | Revokes a pending invite. Only the creator can revoke an invite. Accepted invites cannot be revoked (delete the connection instead). operationId: revokePartnerInvite security: - bearerAuth: - read:attribution:aggregate parameters: - name: token in: path required: true description: The invite token schema: type: string responses: '204': description: Invite revoked '401': $ref: '#/components/responses/Unauthorized' '403': description: Not authorized to revoke this invite content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Invite not found content: application/json: schema: $ref: '#/components/schemas/Error' components: securitySchemes: bearerAuth: type: oauth2 description: OAuth 2.0 authorization with access tokens flows: authorizationCode: authorizationUrl: https://wefunder.com/oauth/authorize tokenUrl: https://wefunder.com/oauth/token scopes: read:public: Read public deal data (explore offerings); requires no user context read:profile: Read user profile information read:investments: Read user investment data read:campaigns: Read campaign information for companies you founded read:attribution:aggregate: Read aggregate attribution statistics (Tier 0) read:attribution:anonymized: Read anonymized attribution data (Tier 1 - requires approval) read:attribution:full: Read full attribution data with investor PII (Tier 2 - founders only) admin:attribution: Administrative access to attribution system (Tier 3 - Wefunder admins only) read:syndicates: View syndicates, members, deals, and portfolio write:syndicates: Manage members, update settings, operate on deals read:spvs: View partner SPVs, their invites, sessions, and investments write:spvs: Create and manage partner SPVs, invites, and investment sessions read:webhooks: View webhook subscriptions write:webhooks: Create, update, and delete webhook subscriptions clientCredentials: tokenUrl: https://wefunder.com/oauth/token scopes: read:public: Read public deal data. The only scope a server-side (client_credentials) key may hold — it acts as the app, with no user, so it can never read user-scoped data. parameters: campaign_id: name: campaign_id in: path required: true description: The fundraise/campaign ID schema: type: integer example: 789 syndicate_id: name: syndicate_id in: path required: true description: The syndicate ID schema: type: integer example: 42 member_id: name: member_id in: path required: true description: The member ID schema: type: string example: cr_123 fundraise_id: name: fundraise_id in: path required: true description: The deal (fundraise) ID schema: type: integer example: 99 start_date: name: start_date in: query description: | Filter results to attributions on or after this date. Format: YYYY-MM-DD. Defaults to 90 days ago if not specified. schema: type: string format: date example: '2025-01-01' end_date: name: end_date in: query description: | Filter results to attributions on or before this date. Format: YYYY-MM-DD. Defaults to today if not specified. schema: type: string format: date example: '2025-03-31' utm_source: name: utm_source in: query description: Filter by UTM source (e.g., "GoogleAds", "facebook", "Klaviyo") schema: type: string example: GoogleAds utm_campaign: name: utm_campaign in: query description: Filter by UTM campaign name schema: type: string example: winter_2025 cursor: name: cursor in: query description: | Cursor-based pagination token. Use the `next_cursor` value from the previous response's `meta` object to retrieve the next page of results. Omit this parameter to retrieve the first page. schema: type: integer example: 12345 webhook_id: name: webhook_id in: path required: true description: The webhook subscription ID schema: type: integer example: 123 spv_id: name: id in: path required: true description: The SPV ID (prefixed, e.g. `spv_abc123`) schema: type: string example: spv_abc123 invite_id: name: invite_id in: path required: true description: The invite ID (prefixed, e.g. `inv_xyz789`) schema: type: string example: inv_xyz789 investment_session_id: name: session_id in: path required: true description: The investment session ID (prefixed, e.g. `is_abc123`) schema: type: string example: is_abc123 partner_investment_id: name: id in: path required: true description: The investment ID (prefixed, e.g. `invt_def456`) schema: type: string example: invt_def456 webhook_subscription_id: name: id in: path required: true description: The webhook subscription ID (prefixed, e.g. `whs_abc123`) schema: type: string example: whs_abc123 IdempotencyKey: name: Idempotency-Key in: header required: false description: | A unique client-generated key that makes a write request safe to retry. If a request is retried within 24 hours with the same key and body, the original result is returned instead of performing the operation again. Strongly recommended on all `POST` requests that create resources. schema: type: string example: acme-series-a-2025-001 schemas: AttributedInvestmentListEnvelope: type: object properties: data: type: array description: | Returns AnonymizedAttribution or FullAttribution objects depending on detail_level and user permissions. items: oneOf: - $ref: '#/components/schemas/AnonymizedAttribution' - $ref: '#/components/schemas/FullAttribution' meta: allOf: - $ref: '#/components/schemas/PaginationMeta' - type: object properties: detail_level: type: string enum: - anonymized - full description: The detail level returned in this response example: anonymized can_view_full: type: boolean description: Whether the user can request full details example: false AttributionCampaignSummary: type: object properties: id: type: integer example: 123 company_name: type: string example: Example Name company_id: type: integer example: 123 state: type: string example: active access_level: type: integer description: 1=anonymized, 2=detailed (future) example: 1 AttributionCampaignSummaryListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/AttributionCampaignSummary' AttributionUser: type: object properties: id: type: integer example: 123 email: type: string example: user@example.com name: type: string example: Example Name AttributionPartner: type: object nullable: true properties: id: type: integer example: 123 company_name: type: string example: Example Name website: type: string example: https://example.com status: type: string enum: - pending - approved - rejected AttributionCampaignAccess: type: object properties: id: type: integer example: 123 company_name: type: string example: Example Name company_id: type: integer example: 123 access_level: type: integer example: 1 AttributionMe: type: object properties: user: $ref: '#/components/schemas/AttributionUser' partner: $ref: '#/components/schemas/AttributionPartner' campaigns: type: array items: $ref: '#/components/schemas/AttributionCampaignAccess' AttributionMeEnvelope: type: object properties: data: $ref: '#/components/schemas/AttributionMe' ConnectedAppRevocation: type: object properties: revoked: type: boolean example: true tokens_revoked: type: integer example: 2 ConnectedAppRevocationEnvelope: type: object properties: data: $ref: '#/components/schemas/ConnectedAppRevocation' WebhookTestResult: type: object properties: message: type: string example: Test webhook queued for delivery delivery_id: type: string format: uuid example: f47ac10b-58cc-4372-a567-0e02b2c3d479 payload_preview: type: object description: The payload that was sent WebhookTestResultEnvelope: type: object properties: data: $ref: '#/components/schemas/WebhookTestResult' ConnectedAppListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/ConnectedApp' DealInvestorListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/DealInvestor' meta: type: object properties: has_more: type: boolean count: type: integer description: Number of investors on this page MemberInvestmentListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/MemberInvestment' meta: type: object properties: count: type: integer description: Number of investments total_amount: type: string description: Sum of all investment amounts in cents, as a string example: '800000' OfferingListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Offering' meta: type: object properties: total_count: type: integer description: Total number of offerings across all pages. example: 119 page_count: type: integer description: Number of offerings on this page (at most 25). example: 25 has_more: type: boolean example: true sort: type: string description: The sort applied to this response. example: most_raised next_cursor: type: integer nullable: true description: Opaque pagination cursor — pass as `cursor` to fetch the next page. Present only when `has_more` is true. example: 25 PartnerInviteListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/PartnerInvite' PartnerWebhookSubscriptionListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/PartnerWebhookSubscription' SpvWithMetaEnvelope: type: object properties: data: $ref: '#/components/schemas/Spv' meta: type: object properties: request_id: type: string example: req_abc123 SyndicateDealListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/SyndicateDeal' meta: type: object properties: count: type: integer WebhookSubscriptionListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/WebhookSubscription' meta: type: object properties: total: type: integer description: Number of active subscriptions example: 2 limit: type: integer description: Maximum subscriptions per application example: 10 WebhookSubscriptionWithRecentDeliveriesEnvelope: type: object properties: data: allOf: - $ref: '#/components/schemas/WebhookSubscription' - type: object properties: recent_deliveries: type: array description: Last 10 delivery attempts items: $ref: '#/components/schemas/WebhookDelivery' WebhookSubscriptionWithSecret2Envelope: type: object properties: data: allOf: - $ref: '#/components/schemas/WebhookSubscription' - type: object properties: secret: type: string description: New HMAC signing secret example: whsec_a1b2c3d4e5f6g7h8i9j0... WebhookSubscriptionWithSecretEnvelope: type: object properties: data: allOf: - $ref: '#/components/schemas/WebhookSubscription' - type: object properties: secret: type: string description: | HMAC signing secret. **Only returned on create.** Store this securely - it cannot be retrieved later. example: whsec_a1b2c3d4e5f6g7h8i9j0... AttributionStatsEnvelope: type: object properties: data: $ref: '#/components/schemas/AttributionStats' AuditEventListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/AuditEvent' meta: $ref: '#/components/schemas/PaginationMeta' CampaignListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Campaign' meta: $ref: '#/components/schemas/PaginationMeta' IntentEnvelope: type: object properties: data: $ref: '#/components/schemas/Intent' IntentListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Intent' meta: $ref: '#/components/schemas/PaginationMeta' InvestmentLinkEnvelope: type: object properties: data: $ref: '#/components/schemas/InvestmentLink' InvestmentListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Investment' meta: $ref: '#/components/schemas/PaginationMeta' InvestmentSessionEnvelope: type: object properties: data: $ref: '#/components/schemas/InvestmentSession' InvestorEnvelope: type: object properties: data: $ref: '#/components/schemas/Investor' InvestorListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Investor' meta: $ref: '#/components/schemas/PaginationMeta' InviteEnvelope: type: object properties: data: $ref: '#/components/schemas/Invite' InviteListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Invite' meta: $ref: '#/components/schemas/PaginationMeta' MarketingPartnerEnvelope: type: object properties: data: $ref: '#/components/schemas/MarketingPartner' OfferingEnvelope: type: object properties: data: $ref: '#/components/schemas/Offering' PartnerInvestmentEnvelope: type: object properties: data: $ref: '#/components/schemas/PartnerInvestment' PartnerInvestmentListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/PartnerInvestment' meta: $ref: '#/components/schemas/PaginationMeta' PartnerInviteEnvelope: type: object properties: data: $ref: '#/components/schemas/PartnerInvite' PartnerWebhookEventEnvelope: type: object properties: data: $ref: '#/components/schemas/PartnerWebhookEvent' PartnerWebhookSubscriptionEnvelope: type: object properties: data: $ref: '#/components/schemas/PartnerWebhookSubscription' PartnerWebhookSubscriptionWithSecretEnvelope: type: object properties: data: $ref: '#/components/schemas/PartnerWebhookSubscriptionWithSecret' SpvCloseStatusEnvelope: type: object properties: data: $ref: '#/components/schemas/SpvCloseStatus' SpvEnvelope: type: object properties: data: $ref: '#/components/schemas/Spv' SpvListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Spv' meta: $ref: '#/components/schemas/PaginationMeta' SyndicateDealDetailEnvelope: type: object properties: data: $ref: '#/components/schemas/SyndicateDealDetail' SyndicateDealEnvelope: type: object properties: data: $ref: '#/components/schemas/SyndicateDeal' SyndicateDetailEnvelope: type: object properties: data: $ref: '#/components/schemas/SyndicateDetail' SyndicateListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Syndicate' meta: $ref: '#/components/schemas/PaginationMeta' SyndicateMemberEnvelope: type: object properties: data: $ref: '#/components/schemas/SyndicateMember' SyndicateMemberListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/SyndicateMember' meta: $ref: '#/components/schemas/PaginationMeta' SyndicateStatisticsEnvelope: type: object properties: data: $ref: '#/components/schemas/SyndicateStatistics' UserEnvelope: type: object properties: data: $ref: '#/components/schemas/User' WebhookSubscriptionEnvelope: type: object properties: data: $ref: '#/components/schemas/WebhookSubscription' Offering: type: object description: | A public offering (a fundraise), addressed by its `ofr_` external id. Returned by the Explore endpoints. Monetary fields are USD decimal strings — parse them as decimals, not floats. properties: id: type: string description: The offering's stable external id. example: ofr_yw3OhusvpfJP4Pk3wRYV1F2R type: type: string example: offering attributes: type: object properties: company_name: type: string example: ZeroBorder Self-Banking Centers tagline: type: string nullable: true example: Example tagline url: type: string description: The company's Wefunder profile URL. example: https://wefunder.com/zeroborder card_image_url: type: string nullable: true description: Absolute URL of the explore card image, or null if the company has none. example: https://uploads.wefunder.com/uploads/company/custom_card_photo/189517/large_card.jpg logo_url: type: string nullable: true description: Absolute URL of the company logo, or null if the company has none. example: https://uploads.wefunder.com/uploads/company/logo/189517/large_logo.png exemption: $ref: '#/components/schemas/Exemption' security_type: type: string description: The financial instrument (e.g. safe, convertible_note, equity, priced_round, fund). example: safe state: type: string example: open testing_the_waters: type: boolean description: | True for Testing-the-Waters rounds, which collect non-binding **reservations**, not investments. Use "reserve"/"reservation" copy for these, "invest"/"investment" otherwise. example: false funding_target: type: string nullable: true description: Funding target in USD, as a decimal string. example: '1000000' min_investment: type: string nullable: true description: Minimum investment in USD, as a decimal string. example: '100' amount_raised: type: string nullable: true description: Amount raised so far in USD, as a decimal string (hellbanned investors excluded). example: '642300' investor_count: type: integer nullable: true description: Distinct investor count (hellbanned investors excluded). example: 1203 started_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' closed_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' company: type: object description: Reference to the parent company. properties: id: type: string description: The company's `co_` external id. example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY type: type: string example: company etag: type: string description: | Content digest of this offering's public payload, for client-side change detection when polling. example: a1b2c3d4e5f6 Exemption: type: object description: The offering's SEC exemption, in market vocabulary. properties: family: type: string enum: - reg_cf - reg_d - reg_a - reg_s - ecsp example: reg_cf subtype: type: string nullable: true description: Sub-type/flavor — `506b` or `506c` for the `reg_d` family, otherwise null. example: null label: type: string description: Human-readable label in normal market language. example: Reg CF User: type: object properties: id: type: integer example: 123 type: type: string example: user attributes: type: object properties: email: type: string example: user@example.com username: type: string example: john_doe name: type: string example: John full_name: type: string example: John Doe bio: type: string nullable: true example: Entrepreneur and investor city: type: string nullable: true example: San Francisco country: type: string nullable: true example: USA avatar_url: type: string nullable: true example: https://example.com/avatar.jpg created_at: type: string format: date-time example: '2023-01-15T10:30:00Z' certified_at: type: string format: date-time nullable: true example: '2023-02-01T14:20:00Z' Investment: type: object properties: id: type: integer example: 456 type: type: string example: investment attributes: type: object properties: amount: type: number format: float example: 1000 state: type: string example: confirmed confirmed_at: type: string format: date-time nullable: true example: '2023-03-15T12:00:00Z' created_at: type: string format: date-time example: '2023-03-10T10:30:00Z' updated_at: type: string format: date-time example: '2023-03-15T12:00:00Z' fundraise_id: type: integer example: 789 campaign: type: object nullable: true properties: id: type: integer example: 789 company_name: type: string example: My Startup Inc. company_id: type: integer example: 101 Campaign: type: object properties: id: type: integer example: 789 type: type: string example: campaign attributes: type: object properties: state: type: string example: active company_id: type: integer example: 101 company_name: type: string example: My Startup Inc. company_url: type: string nullable: true example: my-startup-inc created_at: type: string format: date-time example: '2023-01-10T09:00:00Z' updated_at: type: string format: date-time example: '2023-03-15T14:30:00Z' closed_at: type: string format: date-time nullable: true example: '2023-06-30T23:59:59Z' amount_raised: type: number format: float nullable: true example: 25000 investor_count: type: integer example: 42 PaginationMeta: type: object properties: count: type: integer example: 25 has_more: type: boolean example: true next_cursor: type: integer nullable: true example: 12345 Error: type: object properties: error: type: object properties: type: type: string example: unauthorized message: type: string example: Invalid or expired token details: type: object additionalProperties: true AttributionStats: type: object description: Aggregate attribution statistics for a campaign properties: campaign_id: type: integer example: 789 period: type: object properties: start_date: type: string format: date example: '2025-01-01' end_date: type: string format: date example: '2025-03-31' totals: type: object properties: investment_count: type: integer description: Total number of attributed investments example: 142 total_amount: type: number format: float description: Total dollar amount of attributed investments example: 425000 unique_investors: type: integer description: Count of unique investors example: 138 conversion_rate: type: number format: float description: UTM clicks to investments (percentage) example: 3.08 quality_breakdown: type: object description: Attribution quality metrics properties: direct: type: integer description: Conversions within 1 hour of click example: 98 assisted: type: integer description: Conversions 1-24 hours after click example: 35 delayed: type: integer description: Conversions over 24 hours after click example: 9 by_source: type: array description: Breakdown by UTM source items: type: object properties: utm_source: type: string example: GoogleAds investment_count: type: integer example: 47 total_amount: type: number format: float example: 142500 by_campaign: type: array description: Breakdown by UTM campaign items: type: object properties: utm_campaign: type: string example: winter_2025 investment_count: type: integer example: 47 total_amount: type: number format: float example: 142500 AnonymizedAttribution: type: object description: | An attributed investment with anonymized investor data. No PII is exposed - investor identity is represented by opaque tokens. properties: investment_token: type: string description: Opaque token identifying the investment (32 characters) example: inv_abc123xyz789def456uvw012ghi345 investor_token: type: string description: | Opaque token identifying the investor (32 characters). Same investor gets different tokens for different campaigns. example: usr_def456uvw789abc123xyz012ghi345 amount_tier: type: string description: | Investment amount tier (not exact value). Ranges: small ($1-999), medium ($1,000-9,999), large ($10,000+) enum: - small - medium - large example: medium invested_at: type: string format: date-time description: When the investment was applied example: '2025-02-15T14:30:00Z' status: type: object description: Current investment status properties: bucket: type: string description: Investment processing stage (matches /manage page) enum: - draft - no_payment_yet - pending - is_ready - confirmed - canceled example: confirmed progress_bar: type: boolean description: Whether the investment appears on founder progress bar (likely to complete) example: true attribution: type: object description: UTM attribution data properties: utm_source: type: string example: GoogleAds utm_campaign: type: string example: winter_2025 utm_medium: type: string nullable: true example: cpc utm_content: type: string nullable: true example: video_ad_v2 clicked_at: type: string format: date-time description: When the UTM link was clicked example: '2025-02-15T14:15:00Z' time_to_invest_bucket: type: string description: Time from click to investment enum: - direct - assisted - delayed example: direct quality_score: type: string description: Attribution quality assessment enum: - high - medium - low - suspicious example: high competing_sources: type: integer description: Number of other UTM sources in the attribution window example: 0 WebhookSubscription: type: object description: A webhook subscription for attribution events properties: id: type: integer example: 123 campaign_id: type: integer example: 789 target_url: type: string format: uri example: https://yourapp.com/webhooks/wefunder events: type: array items: type: string enum: - investment.applied - investment.confirmed - investment.canceled example: - investment.applied - investment.confirmed active: type: boolean description: Whether the subscription is active example: true consecutive_failures: type: integer description: Number of consecutive delivery failures (resets on success) example: 0 created_at: type: string format: date-time example: '2025-03-15T10:30:00Z' updated_at: type: string format: date-time example: '2025-03-15T10:30:00Z' WebhookDelivery: type: object description: A single webhook delivery attempt properties: id: type: integer example: 456 delivery_uuid: type: string format: uuid description: Unique delivery identifier (use for idempotency) example: f47ac10b-58cc-4372-a567-0e02b2c3d479 event_type: type: string description: The event that triggered this delivery example: investment.applied status: type: string enum: - pending - delivered - failed example: delivered attempts: type: integer description: Number of delivery attempts example: 1 response_code: type: integer nullable: true description: HTTP status code from the last attempt example: 200 last_attempt_at: type: string format: date-time nullable: true example: '2025-03-15T10:31:00Z' created_at: type: string format: date-time example: '2025-03-15T10:30:00Z' MarketingPartner: type: object description: A registered marketing partner properties: id: type: integer example: 123 user_id: type: integer example: 456 company_name: type: string example: Acme Marketing Agency website: type: string format: uri nullable: true example: https://acme-marketing.com status: type: string enum: - pending - approved - rejected example: approved created_at: type: string format: date-time example: '2025-03-15T10:30:00Z' PartnerInvite: type: object description: An invite to establish a partner-company connection properties: id: type: integer example: 123 token: type: string description: Unique invite token (use in accept URL) example: abc123xyz789 direction: type: string enum: - partner_to_founder - founder_to_partner description: Who initiated the invite example: partner_to_founder status: type: string enum: - pending - accepted - expired - revoked example: pending access_level: type: integer description: Access level granted on acceptance (1=anonymized, 2=detailed) example: 1 company_id: type: integer nullable: true description: Target company (null for partner_to_founder until accepted) example: 789 company_name: type: string nullable: true example: My Startup Inc. partner_email: type: string nullable: true description: Partner email (for founder_to_partner invites) example: partner@agency.com expires_at: type: string format: date-time example: '2025-04-15T10:30:00Z' created_at: type: string format: date-time example: '2025-03-15T10:30:00Z' accept_url: type: string format: uri description: URL to accept the invite example: https://wefunder.com/attribution/invites/abc123xyz789 FullAttribution: type: object description: | Full attribution data with investor PII (Tier 2 - founders only). Returned when detail_level=full is requested by an authorized founder. properties: investment_id: type: integer description: Real Wefunder investment ID example: 12345 investment_token: type: string description: Opaque token (same as anonymized response) example: inv_abc123xyz789def456uvw012ghi345 investor: type: object description: Full investor details properties: id: type: integer example: 6789 name: type: string example: John Smith email: type: string format: email example: john@example.com username: type: string example: johnsmith amount: type: number format: float description: Exact investment amount in dollars example: 5000 invested_at: type: string format: date-time example: '2025-02-15T14:30:00Z' status: type: object properties: bucket: type: string enum: - draft - no_payment_yet - pending - is_ready - confirmed - canceled example: confirmed progress_bar: type: boolean example: true attribution: type: object description: UTM attribution data (same as anonymized) properties: utm_source: type: string example: example utm_campaign: type: string example: example utm_medium: type: string nullable: true example: example utm_content: type: string nullable: true example: example clicked_at: type: string format: date-time example: '2025-03-01T12:00:00Z' time_to_invest_bucket: type: string enum: - direct - assisted - delayed quality_score: type: string enum: - high - medium - low - suspicious competing_sources: type: integer example: 1 Syndicate: type: object description: JSON:API resource representing a syndicate properties: id: type: integer example: 42 type: type: string example: syndicate attributes: type: object properties: name: type: string example: Acme Syndicate slug: type: string example: acme-syndicate tagline: type: string nullable: true description: Short description / tagline for the syndicate example: Investing in the future of AI description: type: string nullable: true example: Example text avatar_url: type: string nullable: true description: Syndicate icon/avatar URL example: https://uploads.wefunder.com/uploads/club/icon/42/large_avatar.png published: type: boolean example: true launched: type: boolean example: true membership_open: type: boolean description: Whether the syndicate is accepting new members (nil defaults to true) example: true member_count: type: integer example: 47 deal_count: type: integer description: Number of linked deals (fundraises) in this syndicate example: 3 created_by_user_id: type: integer example: 123 created_at: type: string format: date-time example: '2025-03-01T12:00:00Z' updated_at: type: string format: date-time example: '2025-03-01T12:00:00Z' SyndicateDetail: description: Same as Syndicate (show and index use the same serializer) allOf: - $ref: '#/components/schemas/Syndicate' SyndicateMember: type: object description: JSON:API resource representing a syndicate member (ClubRole) properties: id: type: integer example: 123 type: type: string example: syndicate_member attributes: type: object properties: user_id: type: integer nullable: true description: User ID (null for invitees who haven't signed up) example: 123 user_name: type: string nullable: true example: Jane Smith user_email: type: string nullable: true description: | **Moderator-only.** User email (or invite_email for pending invitees). Returns null for non-moderator callers. Requires the requesting user to be a manager/operator of the syndicate. example: user@example.com role: type: string example: member description: Current role (creator, manager, member, invitee, applicant, exiled, resigned, etc.) invite_role: type: string nullable: true description: The role the member was invited as example: admin syndicate_permission: type: string enum: - full_access - operator description: Permission level within the syndicate title: type: string nullable: true description: Custom title for the member example: Example title sort_order: type: integer nullable: true description: Display order position example: 5 carry_percentage_override: type: string nullable: true example: '20.0' description: Override carry percentage for this member avatar_url: type: string nullable: true description: User profile photo URL (null for invitees without a user account) example: https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg bio: type: string nullable: true description: User bio with fallback chain (bio -> thesis -> about). Null for invitees without a user account. example: Example text city: type: string nullable: true description: User's city example: San Francisco country: type: string nullable: true description: User's country example: US last_activity_at: type: string format: date-time nullable: true description: ISO 8601 timestamp of user's last login. Null for invitees without a user account. example: '2025-03-01T12:00:00Z' profile_url: type: string nullable: true description: Relative path to user's profile (e.g. '/janedoe'). Null for invitees without a user account. example: /janedoe tags: type: array items: type: string description: '''Can help with'' tags from the user''s investor profile. Empty array for invitees.' example: - Fundraising - Product Strategy joined_at: type: string format: date-time description: ISO 8601 join date (alias for created_at) example: '2025-03-01T12:00:00Z' investment_total: type: string nullable: true description: Total amount invested in syndicate deals, in cents. String to avoid floating-point precision issues. example: '500000' deal_count: type: integer nullable: true description: Number of syndicate deals the member has invested in example: 2 accredited: type: boolean description: | **Moderator-only.** Whether the user is an accredited investor. Only included when the requesting user is a manager/operator. example: true legal_name: type: string nullable: true description: | **Moderator-only.** Full legal name of the user. Only included when the requesting user is a manager/operator. example: Example Name starred: type: boolean description: | **Moderator-only.** Whether the current moderator has starred this member. Only included when the requesting user is a manager/operator. example: true private_tags: type: array items: type: string description: | **Moderator-only.** Labels assigned by moderators via ClubMemberLabel. Only included when the requesting user is a manager/operator. example: - VIP - Follow up created_at: type: string format: date-time example: '2025-03-01T12:00:00Z' updated_at: type: string format: date-time example: '2025-03-01T12:00:00Z' SyndicateDeal: type: object description: JSON:API resource representing a syndicate deal (Fundraise) properties: id: type: integer example: 99 type: type: string example: syndicate_deal attributes: type: object properties: name: type: string example: Acme Corp Series A state: type: string description: Fundraise state example: open company_name: type: string example: Acme Corp company_id: type: integer description: Company ID example: 1234 company_url: type: string nullable: true description: Company slug/URL path example: acme-corp company_logo_url: type: string nullable: true description: Company logo URL example: https://uploads.wefunder.com/uploads/company/logo/1234/large_logo.png amount_raised: type: string nullable: true description: Amount in escrow in cents, as a string to avoid floating-point precision issues example: '23000000' investor_count: type: integer description: Count of distinct active investors example: 47 funding_target: type: string nullable: true description: Target funding amount in cents, as a string example: '50000000' offering_type: type: string nullable: true description: Offering type (e.g. '506(c)', '4(a)(6)') example: 506(c) structure: type: string nullable: true description: Deal structure (e.g. 'equity', 'safe', 'convertible_note') example: safe min_investment: type: string nullable: true description: Minimum investment amount in cents, as a string example: '10000' max_investment: type: string nullable: true description: Maximum investment amount in cents, as a string example: '5000000' description: type: string nullable: true description: Deal funding purpose / description example: Example text created_at: type: string format: date-time example: '2025-03-01T12:00:00Z' updated_at: type: string format: date-time example: '2025-03-01T12:00:00Z' closed_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' funding_started_at: type: string format: date-time nullable: true description: ISO 8601 timestamp when funding opened example: '2025-03-01T12:00:00Z' SyndicateDealDetail: description: Same as SyndicateDeal (show and index use the same serializer) allOf: - $ref: '#/components/schemas/SyndicateDeal' SyndicatePortfolio: type: object properties: data: type: object properties: syndicate_id: type: integer syndicate_name: type: string snapshot_at: type: string format: date-time totals: type: object properties: total_invested_cents: type: integer total_positions: type: integer active_positions: type: integer exited_positions: type: integer positions: type: array items: type: object properties: company: type: object properties: id: type: integer name: type: string slug: type: string investment: type: object properties: amount_cents: type: integer structure: type: string invested_at: type: string format: date-time status: type: string enum: - active - exited valuation: type: object properties: basis_cents: type: integer current_estimate_cents: type: integer nullable: true multiple: type: number format: float nullable: true DealInvestor: type: object description: | JSON:API resource representing a syndicate member's investment in a specific deal. Only syndicate members' investments are included (scoped by member user IDs). properties: id: type: integer description: Investment ID example: 12345 type: type: string example: deal_investor attributes: type: object properties: user_id: type: integer description: Investor's user ID example: 456 user_name: type: string nullable: true example: Jane Smith user_email: type: string nullable: true description: | **Moderator-only.** Investor's email address. Returns null for non-moderator callers (users who are not a manager/operator). example: user@example.com avatar_url: type: string nullable: true description: Investor's profile photo URL example: https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg amount: type: string description: Investment amount in cents, as a string to avoid floating-point precision issues example: '500000' state: type: string description: Investment state example: confirmed invested_at: type: string format: date-time description: ISO 8601 timestamp when the investment was created example: '2025-03-01T12:00:00Z' MemberInvestment: type: object description: | JSON:API resource representing a member's investment in a syndicate deal. Scoped to the syndicate's selected deals (one per company, matching directory semantics). properties: id: type: integer description: Investment ID example: 789 type: type: string example: member_investment attributes: type: object properties: fundraise_id: type: integer description: Deal (fundraise) ID example: 75496 company_name: type: string nullable: true description: Name of the company the deal is for example: Substack amount: type: string description: Investment amount in cents, as a string to avoid floating-point precision issues example: '500000' state: type: string description: Investment state example: confirmed created_at: type: string format: date-time description: ISO 8601 timestamp when the investment was created example: '2025-03-01T12:00:00Z' SyndicateStatistics: type: object description: Aggregate metrics for a syndicate properties: type: type: string example: syndicate_statistics attributes: type: object properties: total_members: type: integer description: Count of active members (excludes resigned and exiled, filters out soft-deleted and hellbanned users) example: 45 members_by_role: type: object description: Member count grouped by role additionalProperties: type: integer example: manager: 3 member: 35 invitee: 5 creator: 1 applicant: 1 total_deals: type: integer description: Total number of linked deals (all linked fundraises) example: 3 live_deals: type: integer description: Number of currently live deals (open/oversubscribed/closing states) example: 1 total_raised: type: string description: | Total amount raised across directory-selected deals (one per company), in cents. String to avoid floating-point precision issues. example: '10780000' total_investors: type: integer description: Count of distinct investors across directory-selected deals (one per company) example: 6527 recent_activity_count: type: integer description: Count of audit events in the last 30 days for this syndicate example: 4 Intent: type: object description: JSON:API resource representing an intent (proposed dangerous action) properties: id: type: string format: uuid example: a1b2c3d4-e5f6-7890-abcd-ef1234567890 type: type: string example: intent attributes: type: object properties: action: type: string example: syndicates.close_deal status: type: string enum: - pending - approved - executing - executed - expired - rejected - failed example: pending resource_type: type: string example: Club resource_id: type: integer example: 42 impact_summary: type: string example: Close the Acme Corp Series A deal. 47 investors have committed $2.3M. review_url: type: string format: uri example: https://wefunder.com/intents/a1b2c3d4-e5f6-7890-abcd-ef1234567890/review description: URL where a human can review and approve/reject this intent requested_by_agent: type: string nullable: true description: Human-readable name of the agent that proposed this intent example: Claude via MCP expires_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' approved_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' executed_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' rejection_reason: type: string nullable: true example: Example text execution_result: type: object nullable: true description: Action-specific result data (only present when status is executed) created_at: type: string format: date-time example: '2025-03-01T12:00:00Z' updated_at: type: string format: date-time example: '2025-03-01T12:00:00Z' AuditEvent: type: object description: JSON:API resource representing an audit event properties: id: type: integer example: 123 type: type: string example: audit_event attributes: type: object properties: occurred_at: type: string format: date-time example: '2025-03-01T12:00:00Z' actor_type: type: string description: Type of actor (e.g. user, agent, system) example: example actor_name: type: string example: ChatGPT via Wefunder MCP action: type: string example: syndicates.member.invited resource_type: type: string example: Club resource_id: type: integer example: 123 resource_label: type: string nullable: true example: Acme Syndicate old_values: type: object nullable: true description: Previous values of changed fields new_values: type: object nullable: true description: New values of changed fields changed_fields: type: array nullable: true items: type: string description: List of field names that changed status: type: string enum: - success - failure - denied error_message: type: string nullable: true example: Example text intent_id: type: string nullable: true description: Associated intent ID if this event was triggered by an intent example: example created_at: type: string format: date-time example: '2025-03-01T12:00:00Z' ConnectedApp: type: object description: An OAuth application authorized by the user properties: id: type: integer example: 123 type: type: string example: connected_app attributes: type: object properties: name: type: string example: ChatGPT via Wefunder MCP scopes: type: array items: type: string example: - read:syndicates - write:syndicates description: All scopes across active tokens for this app last_active_at: type: string format: date-time nullable: true description: When the most recent token was last used example: '2025-03-01T12:00:00Z' action_count: type: integer description: Total audit events for this app example: 34 created_at: type: string format: date-time nullable: true description: When the user first authorized this app example: '2025-03-01T12:00:00Z' ValidationError: type: object description: | Returned on `422 Unprocessable Entity` when request fields fail validation. `details` is an array of per-field problems. properties: error: type: object properties: type: type: string example: validation_error message: type: string example: Validation failed details: type: array items: type: object properties: field: type: string example: terms.minimum_investment_cents code: type: string example: too_low message: type: string example: Minimum investment must be at least $1,000 (100000 cents) SpvTerms: type: object description: Investment terms for an SPV. required: - structure - valuation_cap_cents - minimum_investment_cents - target_raise_cents properties: structure: type: string enum: - safe - convertible_note - equity example: safe safe_type: type: string description: Required when `structure` is `safe`. enum: - post_money - pre_money nullable: true example: post_money valuation_cap_cents: type: integer example: 1000000000 discount_percent: type: integer nullable: true description: Discount percentage (e.g. 20 for 20%). example: null minimum_investment_cents: type: integer example: 1000000 target_raise_cents: type: integer example: 100000000 max_raise_cents: type: integer nullable: true example: 200000000 SpvSettings: type: object properties: accreditation_type: type: string description: '`self_attestation` (506b) or `verified` (506c).' enum: - self_attestation - verified default: self_attestation auto_close_on_target: type: boolean default: false description: Automatically begin closing when the target raise is reached. notify_on_investment: type: boolean default: true description: Send a partner webhook on each new investment. TargetCompanyInput: type: object required: - name properties: name: type: string example: Acme Inc legal_name: type: string example: Acme Inc. website: type: string example: https://acme.com description: type: string example: AI-powered productivity tools state_of_incorporation: type: string description: Two-letter US state code. example: DE SpvCreateInput: type: object required: - name - target_company - terms properties: name: type: string example: Acme Series A SPV target_company: $ref: '#/components/schemas/TargetCompanyInput' terms: $ref: '#/components/schemas/SpvTerms' settings: $ref: '#/components/schemas/SpvSettings' metadata: type: object additionalProperties: true description: Arbitrary partner-defined key-value pairs echoed back on the SPV. example: partner_reference: acme-series-a-2025 internal_notes: Introduced via Demo Day SpvMetrics: type: object properties: total_raised_cents: type: integer example: 5000000 investor_count: type: integer example: 12 confirmed_count: type: integer example: 8 pending_count: type: integer example: 4 average_investment_cents: type: integer example: 416666 Spv: type: object properties: id: type: string example: spv_abc123 type: type: string example: spv attributes: type: object properties: name: type: string example: Acme Series A SPV status: type: string enum: - draft - open - closing - closed - canceled - dissolved example: open series_name: type: string example: Acme Series A SPV, a series of Wefunder LLC invest_url: type: string nullable: true example: https://wefunder.com/invest/spv_abc123 target_company: type: object properties: id: type: string example: co_xyz789 name: type: string example: Acme Inc terms: $ref: '#/components/schemas/SpvTerms' metrics: $ref: '#/components/schemas/SpvMetrics' metadata: type: object additionalProperties: true created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' opened_at: type: string format: date-time nullable: true closing_at: type: string format: date-time nullable: true closed_at: type: string format: date-time nullable: true SpvCloseStatus: type: object properties: spv_id: type: string example: spv_abc123 status: type: string enum: - closing - closed example: closing steps: type: array items: type: object properties: name: type: string enum: - investment_cutoff - pending_resolution - final_tally - disbursement - cap_table example: pending_resolution status: type: string enum: - pending - in_progress - complete example: in_progress completed_at: type: string format: date-time nullable: true pending_count: type: integer nullable: true example: 3 estimated_completion: type: string format: date-time nullable: true example: '2025-02-22T00:00:00Z' InviteCreateInput: type: object required: - email properties: email: type: string example: investor@example.com first_name: type: string example: Jane last_name: type: string example: Investor allocation_cents: type: integer nullable: true description: Optional reserved allocation for this investor. example: 5000000 message: type: string description: Optional personal message included in the invite email. example: Excited to have you in this deal! send_email: type: boolean default: true Invite: type: object properties: id: type: string example: inv_xyz789 type: type: string example: invite attributes: type: object properties: email: type: string example: investor@example.com status: type: string enum: - pending - opened - invested - expired - revoked example: pending allocation_cents: type: integer nullable: true example: 5000000 invest_url: type: string example: https://wefunder.com/invest/spv_abc123?invite=inv_xyz789 investment_id: type: string nullable: true example: invt_def789 created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' opened_at: type: string format: date-time nullable: true invested_at: type: string format: date-time nullable: true expires_at: type: string format: date-time nullable: true example: '2025-02-14T10:00:00Z' InvestmentLinkCreateInput: type: object properties: allocation_cents: type: integer nullable: true description: Optional per-investor allocation cap applied to anyone using the link. example: 5000000 max_uses: type: integer nullable: true description: Optional cap on how many investments the link can produce. example: 50 InvestmentLink: type: object properties: id: type: string example: il_abc123 type: type: string example: investment_link attributes: type: object properties: url: type: string example: https://wefunder.com/invest/spv_abc123?link=il_abc123 allocation_cents: type: integer nullable: true example: 5000000 max_uses: type: integer nullable: true example: 50 uses_count: type: integer example: 0 active: type: boolean example: true created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' InvestmentSessionCreateInput: type: object description: | Either `email` (invite an unknown recipient) or `wefunder_user_id` (target an existing Wefunder user) should be provided. properties: email: type: string nullable: true example: investor@example.com wefunder_user_id: type: string nullable: true example: usr_existing123 allocation_cents: type: integer nullable: true example: 5000000 success_url: type: string nullable: true description: Where to send the recipient after they complete the session. example: https://partner.com/done InvestmentSession: type: object properties: id: type: string example: is_abc123 type: type: string example: investment_session attributes: type: object properties: status: type: string enum: - pending - in_progress - completed - abandoned - expired - canceled example: pending url: type: string description: The hosted URL the recipient opens to begin the flow. example: https://wefunder.com/invest/session/is_abc123 spv_id: type: string example: spv_abc123 email: type: string nullable: true example: investor@example.com allocation_cents: type: integer nullable: true example: 5000000 kyc_status: type: string enum: - pending - passed - failed nullable: true example: pending funding_status: type: string enum: - pending - succeeded - card_declined - insufficient_funds nullable: true example: pending investment_id: type: string nullable: true description: Populated once the session completes successfully. example: invt_def789 created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' expires_at: type: string format: date-time nullable: true PartnerInvestment: type: object properties: id: type: string example: invt_abc123 type: type: string example: investment attributes: type: object properties: amount_cents: type: integer example: 2500000 status: type: string enum: - pending - awaiting_payment - pending_accreditation - confirmed - canceled example: confirmed spv_id: type: string example: spv_abc123 investor_id: type: string nullable: true example: investor_usr456 accreditation: type: object nullable: true properties: type: type: string enum: - self_attestation - verified example: verified status: type: string enum: - pending - passed - failed - expired example: pending basis: type: string nullable: true example: income submitted_at: type: string format: date-time nullable: true confirmed_at: type: string format: date-time nullable: true created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' Investor: type: object description: | An investor known to the partner. PII fields (`full_name`, `email`) are only populated when the token holds the required access tier; otherwise they are null. properties: id: type: string example: investor_usr456 type: type: string example: investor attributes: type: object properties: full_name: type: string nullable: true example: Jane Investor email: type: string nullable: true example: investor@example.com accredited: type: boolean nullable: true example: true total_invested_cents: type: integer example: 7500000 investment_count: type: integer example: 2 created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' PartnerWebhookSubscriptionCreateInput: type: object required: - url - events properties: url: type: string example: https://partner.com/webhooks/wefunder events: type: array items: type: string example: - spv.opened - spv.closed - investment.created - investment.confirmed PartnerWebhookSubscription: type: object properties: id: type: string example: whs_abc123 type: type: string example: webhook_subscription attributes: type: object properties: url: type: string example: https://partner.com/webhooks/wefunder events: type: array items: type: string example: - spv.opened - spv.closed active: type: boolean example: true created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' PartnerWebhookSubscriptionWithSecret: allOf: - $ref: '#/components/schemas/PartnerWebhookSubscription' - type: object properties: attributes: type: object properties: secret: type: string description: | The signing secret, shown only once at creation. Used to verify the `X-Wefunder-Signature` header on delivered events. example: whsec_abc123def456 PartnerWebhookEvent: type: object description: The payload delivered to a subscribed webhook URL. properties: id: type: string example: evt_abc123 event: type: string description: | The event type. SPV: `spv.created`, `spv.opened`, `spv.updated`, `spv.closing`, `spv.closed`, `spv.canceled`. Invites/sessions: `invite.created`, `invite.opened`, `invite.invested`, `invite.expired`, `investment_session.created`, `investment_session.completed`. Investments: `investment.created`, `investment.confirmed`, `investment.canceled`, `investment.accreditation_verified`, `investment.accreditation_failed`. Disbursement: `disbursement.initiated`, `disbursement.completed`, `disbursement.failed`. Plus `webhook.test`. example: investment.created created_at: type: string format: date-time example: '2025-01-15T15:00:00Z' data: type: object additionalProperties: true description: Event-specific payload (e.g. the affected SPV and investment). partner_reference: type: string nullable: true description: Echoes the SPV's `metadata.partner_reference` when present. example: acme-series-a-2025 responses: Unauthorized: description: | Authentication required or token is invalid/expired. This error occurs when: - No Authorization header is provided - The access token is invalid or malformed - The access token has expired - The access token has been revoked To resolve: Obtain a new access token using the OAuth 2.0 flow. content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: | The authenticated user does not have permission to access this resource. This typically means: - The user is authenticated but lacks the required OAuth scope - The resource belongs to a different user - The user's role doesn't allow this operation Check that your OAuth token includes the necessary scopes for this endpoint. content: application/json: schema: $ref: '#/components/schemas/Error' RateLimitExceeded: description: | Too many requests in a short time period. The API enforces rate limits to ensure fair usage and system stability. When you exceed the limit, you'll receive this error along with headers indicating when you can retry. Check the `X-RateLimit-Reset` header to know when your limit will reset. Consider implementing exponential backoff in your application. headers: X-RateLimit-Limit: schema: type: integer description: Request limit per hour X-RateLimit-Remaining: schema: type: integer description: Remaining requests X-RateLimit-Reset: schema: type: integer description: UTC timestamp when the limit resets content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: The requested resource does not exist or is not visible to this token. content: application/json: schema: $ref: '#/components/schemas/Error' Conflict: description: | The request conflicts with the resource's current state (for example, opening an SPV that is already open, or closing one that is not yet open). content: application/json: schema: $ref: '#/components/schemas/Error' ValidationFailed: description: One or more request fields failed validation. content: application/json: schema: $ref: '#/components/schemas/ValidationError' NotImplemented: description: | This endpoint is defined but not yet implemented. It is published so partners and tooling can build against the contract ahead of release. content: application/json: schema: $ref: '#/components/schemas/Error' SandboxOnly: description: | This endpoint only works with sandbox (`at_test_`) tokens. It was called with a production (`at_live_`) token and refused, because it mints fake investors and money. content: application/json: schema: $ref: '#/components/schemas/Error'