# 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 id (`ofr_...`). 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- // Browse live offerings, sorted. The cursor is opaque and handled for you. const page = await wf.offerings.list({ sort: "most_raised" }); for (const offering of page.data ?? []) { console.log(offering.id, offering.attributes?.company_name); } // Or stream every offering lazily, one page fetched at a time: for await (const offering of wf.offerings.all({ sort: "newest" })) { console.log(offering.id); } /offerings/{external_id}: get: tags: - Explore summary: Get a public offering description: | Retrieves a single public offering by its id (`ofr_...`). 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 id (`ofr_...`). 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const offering = await wf.offerings.get("ofr_QfmTyP8qjfYAvkEgREyL3kLf"); console.log(offering.attributes?.company_name, offering.attributes?.state); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const me = await wf.users.me(); console.log("Logged in as", me.id); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- // Auto-paginate the full history lazily: for await (const investment of wf.investments.all()) { console.log(investment.id); } // Or grab one page with its meta (next_cursor, etc.): const page = await wf.investments.list(); console.log(page.data?.length, page.meta?.next_cursor); /portfolio: get: tags: - Portfolio summary: Get portfolio summary description: | Returns totals across the authenticated investor's portfolio: cost basis, current value, realized and unrealized gains, and per-status position counts. Accepts the same `status` and `company` filters as `GET /portfolio/positions` to total a slice of the portfolio, such as only exited positions. Requires a user-authorized token. App-only (client credentials) tokens receive 403. Portfolio values are recalculated periodically rather than on each request, so recent investments and valuation changes can take a few minutes to appear. `as_of` is the timestamp of the oldest calculation included in the response. Monetary values are **integer cents**; per-share prices and return multiples are **decimal strings**. operationId: getPortfolio security: - bearerAuth: - read:investments parameters: - name: status in: query description: Total only positions with this status. schema: type: string enum: - active - exited - sold - failed - name: company in: query description: Total only positions in this company, by its id (`co_...`). Unknown ids return 404. schema: type: string example: co_2bt9eJn6X8C9gnShl63qlzkK responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/PortfolioSummaryEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/RateLimitExceeded' /portfolio/positions: get: tags: - Portfolio summary: List portfolio positions description: | Returns the authenticated investor's positions, one per offering (fundraise), newest first. Position totals cover everything the investor holds in that offering; `securities` breaks the total down by security offering and legal owner, so early bird tiers and personally-held vs entity-held stakes each appear separately. Fund and SPV positions identify the companies the vehicle invested in under `holdings`. To compute an average cost per share, divide `cost_basis_cents` by `shares_held`. Requires a user-authorized token, like `GET /portfolio`. operationId: listPortfolioPositions security: - bearerAuth: - read:investments parameters: - $ref: '#/components/parameters/cursor' - name: per_page in: query description: Page size, 1-100. Defaults to 25. schema: type: integer default: 25 maximum: 100 - name: status in: query description: Filter positions by status. schema: type: string enum: - active - exited - sold - failed - name: company in: query description: Filter to a single company by its id (`co_...`). Unknown ids return 404. schema: type: string example: co_2bt9eJn6X8C9gnShl63qlzkK responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/PortfolioPositionListEnvelope' '400': description: Invalid filter value (unknown status or malformed cursor) content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Filtered company not found content: application/json: schema: $ref: '#/components/schemas/Error' '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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- // Campaigns for companies where the authenticated user is a founder. const page = await wf.campaigns.list(); for (const campaign of page.data ?? []) { console.log(campaign.id, campaign.attributes?.company_name, campaign.attributes?.amount_raised); } // Or stream every campaign lazily, one page fetched at a time: for await (const campaign of wf.campaigns.all()) { console.log(campaign.attributes?.state); } /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const page = await wf.syndicates.list({ limit: 50 }); for (const syndicate of page.data ?? []) { console.log(syndicate.id, syndicate.attributes?.name, syndicate.attributes?.member_count); } /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const syndicate = await wf.syndicates.get(42); console.log(syndicate.attributes?.name, syndicate.attributes?.deal_count); 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}/portfolio: get: tags: - Syndicate Portfolio summary: Get syndicate portfolio summary description: | Returns totals across the syndicate's portfolio: cost basis, current value, gains, per-status deal counts, and the number of distinct investors. Accepts the same `status` and `company` filters as the positions endpoint to total a slice of the portfolio, such as only exited deals. The summary covers the syndicate's funded deals, summed across everyone holding their securities (not just current members), as aggregate figures only. Member-level data is available on the members endpoints. Portfolio values are recalculated periodically rather than on each request, so recent investments and valuation changes can take a few minutes to appear. `as_of` is the timestamp of the oldest calculation included in the response. Monetary values are **integer cents**; per-share prices and return multiples are **decimal strings**. operationId: getSyndicatePortfolio security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - name: status in: query description: Total only deals with this status. schema: type: string enum: - active - exited - sold - failed - name: company in: query description: Total only positions in this company, by its id (`co_...`). Unknown ids return 404. schema: type: string example: co_2bt9eJn6X8C9gnShl63qlzkK responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SyndicatePortfolioSummaryEnvelope' '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}/portfolio/positions: get: tags: - Syndicate Portfolio summary: List syndicate portfolio positions description: | Returns the syndicate's portfolio one deal at a time, newest first. Each position is one fundraise ("offering"), aggregated across everyone holding its securities, with `investor_count` at both the position and security level. `securities` breaks each position down by security offering (for example, an early bird tier vs the regular terms) — most deals have a single entry. Fund and SPV deals identify the companies the vehicle invested in under `holdings`; some older vehicles have no recorded holdings and return an empty array. operationId: listSyndicatePortfolioPositions security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/cursor' - name: per_page in: query description: Page size, 1-100. Defaults to 25. schema: type: integer default: 25 maximum: 100 - name: status in: query description: Filter positions by status. schema: type: string enum: - active - exited - sold - failed - name: company in: query description: Filter to a single company by its id (`co_...`). Unknown ids return 404. schema: type: string example: co_2bt9eJn6X8C9gnShl63qlzkK responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/PortfolioPositionListEnvelope' '400': description: Invalid filter value (unknown status or malformed cursor) content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Syndicate or filtered company not found content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/deals/{fundraise_id}/close: post: tags: - Syndicate Deals summary: Close deal description: | Closing a deal is irreversible and requires human approval through the Intent system. This endpoint server-mints a pending `syndicates.close_deal` intent and returns the deal under `data` plus the intent's `review_url` under `meta.close_deal_intent`. The deal is not transitioned until a permitted human approves the intent in the Wefunder UI. Idempotent: a repeated call returns the same pending intent rather than minting a duplicate. operationId: closeSyndicateDeal security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' responses: '200': description: Close intent proposed content: application/json: schema: $ref: '#/components/schemas/SyndicateDealCloseIntentEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: The deal does not belong to this syndicate, or fundraise_id is missing content: application/json: schema: $ref: '#/components/schemas/Error' '429': description: Too many pending intents for this token (rate limited) content: application/json: schema: $ref: '#/components/schemas/Error' /syndicates/{syndicate_id}/deals/{fundraise_id}/finalize: post: tags: - Syndicate Deals summary: Finalize deal description: | Finalizing a deal is irreversible and requires human approval through the Intent system. This endpoint server-mints a pending `syndicates.finalize_deal` intent and returns the deal under `data` plus the intent's `review_url` under `meta.finalize_deal_intent`. The finalize runs only when a permitted human approves the intent in the Wefunder UI. Idempotent: a repeated call returns the same pending intent rather than minting a duplicate. operationId: finalizeSyndicateDeal security: - bearerAuth: - write:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' responses: '200': description: Finalize intent proposed content: application/json: schema: $ref: '#/components/schemas/SyndicateDealFinalizeIntentEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: The deal does not belong to this syndicate, or fundraise_id is missing content: application/json: schema: $ref: '#/components/schemas/Error' '429': description: Too many pending intents for this token (rate limited) 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: string description: The syndicate's id (`syn_...`). example: syn_aB3xQ9k2vF8mNp1zT5wY7Qc4 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: intent } = await wf.unwrap( wf.raw.createIntent({ body: { action_name: "syndicates.close_deal", resource_type: "Club", resource_id: 42, params: { fundraise_id: 99 }, // Safe to retry: an existing pending/executed intent with this key is returned instead. idempotency_key: "close-acme-series-a", requested_by_agent: "My Deal Bot", }, }), ); // Hand this URL to a human — the action executes only after they approve. console.log(intent?.attributes?.review_url); 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: string description: For Club resources, the syndicate's id (`syn_...`). - 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- // Which of my proposed actions are still waiting on human approval? const page = await wf.intents.list({ status: "pending" }); for (const intent of page.data ?? []) { console.log(intent.id, intent.attributes?.action, intent.attributes?.review_url); } /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const intent = await wf.intents.get("intent_abc123"); console.log(intent.attributes?.status); // pending | approved | executed | expired | rejected | failed /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: string description: | Filter by specific resource ID. Must be used with resource_type. For Club resources, pass the syndicate's id (`syn_...`). - 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- // Everything my connected apps did to syndicate members, most recent first. const activity = await wf.unwrap( wf.raw.listActivity({ query: { action_pattern: "syndicates.member.*", status: "success" }, }), ); for (const event of activity.data ?? []) { console.log(event.attributes?.action, event.attributes?.created_at); } /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const apps = await wf.unwrap(wf.raw.listConnectedApps()); for (const app of apps.data ?? []) { console.log(app.id, app.attributes?.name); } /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const stats = await wf.unwrap( wf.raw.getAttributionStats({ path: { campaign_id: 1234 }, query: { start_date: "2026-01-01", end_date: "2026-03-31", utm_source: "newsletter" }, }), ); console.log(stats.data); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const investments = await wf.unwrap( wf.raw.listAttributedInvestments({ path: { campaign_id: 1234 }, query: { start_date: "2026-01-01", detail_level: "anonymized" }, }), ); for (const investment of investments.data ?? []) { if ("amount_tier" in investment) { // Anonymized row: opaque tokens + an amount tier, never exact values or PII. console.log(investment.investor_token, investment.amount_tier, investment.attribution?.utm_source); } } /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const subscriptions = await wf.unwrap( wf.raw.listWebhookSubscriptions({ path: { campaign_id: 1234 } }), ); for (const sub of subscriptions.data ?? []) { console.log(sub.id, sub.target_url, sub.events, sub.active); } 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const subscription = await wf.unwrap( wf.raw.createWebhookSubscription({ path: { campaign_id: 1234 }, body: { target_url: "https://example.com/wefunder/webhooks", events: ["investment.confirmed", "investment.canceled"], }, }), ); console.log(subscription); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const subscription = await wf.unwrap( wf.raw.getWebhookSubscription({ path: { campaign_id: 1234, webhook_id: 56 } }), ); console.log(subscription.data); 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: 'await wf.unwrap(wf.raw.deleteWebhookSubscription({ path: { campaign_id: 1234, webhook_id: 56 } }));' /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const result = await wf.unwrap( wf.raw.testWebhookSubscription({ path: { campaign_id: 1234, webhook_id: 56 } }), ); console.log(result.data); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const me = await wf.attribution.me(); console.log(me); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const campaigns = await wf.unwrap(wf.raw.listAttributionCampaigns()); console.log(campaigns.data); /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://api.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://api.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's id (`syn_...`). schema: type: string example: syn_aB3xQ9k2vF8mNp1zT5wY7Qc4 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_link_id: name: invite_link_id in: path required: true description: The invite link ID (prefixed, e.g. `il_abc123`) schema: type: string example: il_abc123 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' IntentReview: type: object description: Lightweight handle to a pending intent a permitted human must approve for the action to take effect. The full intent is not readable through this API; the review_url is the approval link. Returned under `meta._intent` by the endpoints that server-mint an intent (partner SPV close/cancel, syndicate deal close/finalize). properties: id: type: string format: uuid description: The intent's id (Intent has a UUID primary key). example: a1b2c3d4-e5f6-7890-abcd-ef1234567890 status: type: string example: pending review_url: type: string format: uri example: https://wefunder.com/intents/a1b2c3d4-e5f6-7890-abcd-ef1234567890/review 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' meta: type: object properties: request_id: type: string InvestmentSessionListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/InvestmentSession' meta: type: object properties: has_more: type: boolean page_count: type: integer request_id: type: string next_cursor: type: integer nullable: true 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' InviteLinkEnvelope: type: object properties: data: $ref: '#/components/schemas/InviteLink' meta: type: object properties: request_id: type: string InviteLinkListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/InviteLink' meta: type: object properties: has_more: type: boolean page_count: type: integer request_id: type: string next_cursor: type: integer nullable: true 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' PartnerInvestorListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/PartnerInvestor' 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' SpvStatusEnvelope: type: object properties: data: $ref: '#/components/schemas/SpvStatus' meta: type: object properties: request_id: type: string example: req_abc123 SpvEnvelope: type: object properties: data: $ref: '#/components/schemas/Spv' SpvCloseIntentEnvelope: type: object description: The SPV plus the pending disburse_funds intent an advisor approves to finalize the close. properties: data: $ref: '#/components/schemas/Spv' meta: type: object properties: disburse_intent: $ref: '#/components/schemas/IntentReview' SpvCancelIntentEnvelope: type: object description: The SPV plus the pending cancel intent an advisor approves to abort the raise and refund investors. properties: data: $ref: '#/components/schemas/Spv' meta: type: object properties: cancel_intent: $ref: '#/components/schemas/IntentReview' 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' SyndicateDealCloseIntentEnvelope: type: object description: The deal plus the pending close_deal intent a permitted human approves to close it. properties: data: $ref: '#/components/schemas/SyndicateDeal' meta: type: object properties: close_deal_intent: $ref: '#/components/schemas/IntentReview' SyndicateDealFinalizeIntentEnvelope: type: object description: The deal plus the pending finalize_deal intent a permitted human approves to finalize it. properties: data: $ref: '#/components/schemas/SyndicateDeal' meta: type: object properties: finalize_deal_intent: $ref: '#/components/schemas/IntentReview' 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 id (`ofr_...`). 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 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 id (`co_...`). 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 request_id: type: string description: Unique identifier for this request. Quote it in support tickets. example: req_abc123 remediation: type: string description: When present, a hint on how to resolve the error. example: Obtain a new access token using the OAuth 2.0 flow. 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: string description: The syndicate's id (`syn_...`). example: syn_aB3xQ9k2vF8mNp1zT5wY7Qc4 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' PortfolioCompanyRef: type: object description: The company behind a portfolio position or holding. properties: id: type: string description: The company's id (`co_...`) example: co_2bt9eJn6X8C9gnShl63qlzkK name: type: string example: Acme Robotics slug: type: string nullable: true example: acme-robotics PortfolioSecurity: type: object description: | One security offering within the position's fundraise, such as an early bird tier or the regular terms. Most positions have a single entry. A position has several when the round sold multiple tiers, or (investor endpoint) when the investor holds the same offering through more than one legal owner. Entries do not have ids; each is described by its `terms`, and occasionally a round has more than one offering record with identical terms. properties: structure: type: string nullable: true description: | Security type (`safe`, `equity`, `convertible_note`, `revenue_share`, `simple_loan`, `priced_round`, `fund`, `custom`). May be null on legacy offerings — fall back to the position-level `structure`. early_bird: type: boolean terms: type: object description: The offering's issue terms. Fields are null when the term does not apply to the security type. properties: share_price: type: string nullable: true description: Issue price per share as a decimal string example: '10.0' note_cap_cents: type: integer nullable: true note_discount: type: string nullable: true premoney_valuation_cents: type: integer nullable: true cost_basis_cents: type: integer current_value_cents: type: integer unrealized_gain_cents: type: integer realized_gain_cents: type: integer shares_held: type: string nullable: true description: Decimal string. Null for non-share structures and multiple-of-basis valuations. current_share_price: type: string nullable: true description: Decimal string, split-adjusted. invested_at: type: string format: date-time nullable: true owner: type: object nullable: true description: | The legal owner of this stake — `{"kind": "individual"}` for personally-held stakes, `{"kind": "entity", "name": "..."}` for stakes held through the investor's own entity (IRA, LLC). Present on the investor endpoint only. properties: kind: type: string enum: - individual - entity name: type: string investor_count: type: integer description: The number of distinct investors aggregated into this entry. Present on the syndicate endpoint only. PortfolioHolding: type: object description: | A company a fund vehicle invested in — one entry per company, with the status of the most recent investment when the fund invested in the same company more than once. properties: company: $ref: '#/components/schemas/PortfolioCompanyRef' status: type: string enum: - active - failed - exited PortfolioPosition: type: object description: | One position per offering (fundraise). On the investor endpoint the totals sum the authenticated investor's stakes; on the syndicate endpoint they sum every holder's stakes and `investor_count` is present. properties: id: type: string description: The offering's id (`ofr_...`) — a position's identity is its offering. example: ofr_9aKxQ2vF8mNp1zT5wY7Qb3Cd type: type: string enum: - portfolio_position - syndicate_portfolio_position attributes: type: object properties: offering_id: type: string example: ofr_9aKxQ2vF8mNp1zT5wY7Qb3Cd company: $ref: '#/components/schemas/PortfolioCompanyRef' asset_type: type: string enum: - company - fund description: '`fund` for SPV and fund vehicles; the underlying companies appear in `holdings` where recorded.' structure: type: string description: The fundraise's security structure (`safe`, `equity`, `fund`, ...) status: type: string enum: - active - exited - sold - failed description: '`sold` means the stake was transferred away (secondary sale).' exit_reason: type: string nullable: true enum: - ipo - acquisition - buyback - failed - winding_down description: | Why the position ended. Set only for `exited` and `failed` positions — a `sold` position ended with the transfer itself, and some older positions have no recorded ending. `ipo` means the company went public; distributions, if any, appear in `realized_gain_cents`. invested_at: type: string format: date-time nullable: true currency: type: string example: usd cost_basis_cents: type: integer current_value_cents: type: integer description: Estimated current value; based on cost basis when no newer valuation has been recorded. unrealized_gain_cents: type: integer description: Can be negative. realized_gain_cents: type: integer return_multiple: type: string nullable: true description: Decimal string. Null when cost basis is zero. example: '1.6492' shares_held: type: string nullable: true current_share_price: type: string nullable: true description: Null when the position's securities carry different prices — see the per-entry values. securities: type: array items: $ref: '#/components/schemas/PortfolioSecurity' holdings: type: array description: Companies the vehicle invested in, for fund positions. Empty for non-fund positions and for vehicles without recorded holdings. items: $ref: '#/components/schemas/PortfolioHolding' investor_count: type: integer description: The number of distinct investors in this deal. Present on the syndicate endpoint only. as_of: type: string format: date-time description: When the values in this position were last calculated (the oldest timestamp when several underlying records are combined). PortfolioPositionListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/PortfolioPosition' meta: type: object properties: has_more: type: boolean page_count: type: integer next_cursor: type: integer nullable: true description: Pass as `cursor` to fetch the next page. Absent on the last page. PortfolioSummaryEnvelope: type: object properties: data: type: object properties: type: type: string example: portfolio_summary attributes: type: object properties: currency: type: string example: usd total_cost_basis_cents: type: integer total_current_value_cents: type: integer total_unrealized_gain_cents: type: integer total_realized_gain_cents: type: integer return_multiple: type: string nullable: true example: '1.3107' position_counts: type: object description: Distinct offerings per status. properties: active: type: integer exited: type: integer sold: type: integer failed: type: integer as_of: type: string format: date-time nullable: true SyndicatePortfolioSummaryEnvelope: type: object properties: data: type: object properties: type: type: string example: syndicate_portfolio_summary attributes: type: object properties: currency: type: string example: usd total_cost_basis_cents: type: integer total_current_value_cents: type: integer total_unrealized_gain_cents: type: integer total_realized_gain_cents: type: integer return_multiple: type: string nullable: true deal_counts: type: object description: Distinct deals per status. properties: active: type: integer exited: type: integer sold: type: integer failed: type: integer investor_count: type: integer description: Distinct investors across the syndicate's funded deals. as_of: type: string format: date-time 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: string description: For Club resources, the syndicate's id (`syn_...`). example: syn_aB3xQ9k2vF8mNp1zT5wY7Qc4 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: string description: For Club resources, the syndicate's id (`syn_...`). example: syn_aB3xQ9k2vF8mNp1zT5wY7Qc4 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 description: Investors with an active investment in this SPV. example: 12 documented_soft_cap: type: integer description: The documented per-SPV investor soft cap (247). Advisory only — it is not enforced, and `investor_count` may exceed it. example: 247 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 PartnerInvestor: type: object description: | An investor in an SPV, aggregated across all their investments in it. `full_name` and `email` are null unless the token holds `read:investors:pii`. properties: id: type: string example: usr_abc123 type: type: string example: investor attributes: type: object properties: full_name: type: string nullable: true example: Ada Lovelace email: type: string nullable: true example: ada@example.com accredited: type: boolean example: true total_invested_cents: type: integer description: Sum of the investor's active investments in this SPV. example: 500000 investment_count: type: integer example: 2 created_at: type: string format: date-time nullable: true description: When the investor first invested in this SPV. example: '2025-01-15T12:00:00Z' SpvStatus: type: object properties: id: type: string example: ofr_abc123 type: type: string example: spv_status attributes: type: object properties: status: type: string enum: - draft - open - closing - closed - canceled example: closing requires_ops_review: type: boolean description: The offering is awaiting Wefunder review before it can open. example: false disbursement_scheduled: type: boolean description: A disbursement is queued for this SPV but funds have not moved yet. example: true expected_disbursement_at: type: string format: date-time nullable: true description: Estimated date funds will be disbursed — 3 business days from when the close executed and the SPV entered the disbursement queue. Present only while `status` is `closing` (awaiting disbursement); null before close and once funds have moved. example: '2026-07-28T00:00:00Z' finalized_investor_list: type: boolean description: The investor roster has been finalized. example: false BulkInviteLinkCreateInput: type: object required: - invite_links properties: invite_links: type: array maxItems: 100 description: Up to 100 per-person invites. Each item must include `email` or `wefunder_user_id`. items: $ref: '#/components/schemas/InviteLinkCreateInput' BulkInviteLinkEnvelope: type: object description: 207 Multi-Status — created links in `data`, per-item failures in `errors`. properties: data: type: array items: $ref: '#/components/schemas/InviteLink' errors: type: array items: type: object properties: index: type: integer description: Position of the failed item in the request array. example: 2 type: type: string example: mutually_exclusive_params detail: type: string example: provide either email or wefunder_user_id, not both meta: type: object properties: created: type: integer failed: type: integer request_id: type: string InviteLinkCreateInput: type: object description: | All fields optional. With no `email`/`wefunder_user_id` the link is **reusable**; with either it becomes a **per-person** invite. `email` and `wefunder_user_id` are mutually exclusive. `max_uses` is rejected for per-person invites; `send_email` is rejected without a recipient. properties: allocation_cents: type: integer nullable: true description: | Budget cap, in cents. Reusable → total budget across all claims of the link. Per-person → the single recipient's cap. example: 5000000 max_uses: type: integer nullable: true description: Claim cap for a reusable link. Null = unlimited. Not allowed for per-person invites. example: 50 email: type: string nullable: true description: Recipient email. Makes the link per-person. Mutually exclusive with `wefunder_user_id`. example: investor@example.com wefunder_user_id: type: string nullable: true description: Id (`usr_...`) of an existing Wefunder user to invite. Mutually exclusive with `email`; must resolve to a user (else 404). example: usr_existing123 first_name: type: string nullable: true description: Recipient first name (per-person only). example: Jane last_name: type: string nullable: true description: Recipient last name (per-person only). example: Investor message: type: string nullable: true description: Personal message included in the invite email (per-person only). example: Excited to have you in this deal! send_email: type: boolean default: true description: Whether to email the invite (per-person only). Rejected on a reusable create. InviteLinkUpdateInput: type: object description: Only link terms are mutable; recipient identity, `reuse`, and the URL token cannot change. Other fields are ignored. properties: allocation_cents: type: integer nullable: true example: 5000000 max_uses: type: integer nullable: true description: Rejected for per-person invites and when set below the current `uses_count`. example: 50 InviteLink: type: object description: | A partner invite link. `id` is the link's `il_...` id (distinct from the URL access `token`, which lives only in `url`). Per-person (`reuse: false`) links carry the extra recipient + derived-status attributes below; reusable links omit them. properties: id: type: string example: il_aB3xQ9k2vF8mNp1zT5wY7Q type: type: string example: invite_link attributes: type: object properties: url: type: string description: Shareable invite URL. Carries the access token, not the id. example: https://wefunder.com/i/abc123token 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 description: False once the link is canceled (soft-deleted). example: true created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' email: type: string nullable: true description: Per-person only — recipient email. example: investor@example.com wefunder_user_id: type: string nullable: true description: Per-person only — the recipient's id (`usr_...`), when they are an existing user. example: usr_existing123 status: type: string nullable: true description: | Per-person only — derived live, not stored. `pending` (sent, not yet opened), `opened` (link clicked), `invested` (a matching active investment exists), `revoked` (canceled). enum: - pending - opened - invested - revoked example: pending opened_at: type: string format: date-time nullable: true description: Per-person only. invested_at: type: string format: date-time nullable: true description: Per-person only. investment_id: type: string nullable: true description: Per-person only — the id (`inv_...`) of the matching investment. example: inv_def789 events: type: array description: Engagement timeline. Present on the show (get-one) endpoint only. items: type: object properties: event: type: string example: send city: type: string nullable: true country_code: type: string nullable: true created_at: type: string format: date-time InvestmentSessionCreateInput: type: object description: | Either `email` (invite an unknown recipient) or `wefunder_user_id` (target an existing Wefunder user) should be provided. `spv_id` is required on `POST /partner/investment_sessions`; on the deprecated SPV-nested path the SPV comes from the URL instead. properties: spv_id: type: string nullable: true description: The SPV to invest into (prefixed, e.g. `spv_abc123`). example: spv_abc123 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 minimum: 100 multipleOf: 100 description: | Fixed investment amount, in cents (whole dollars only). Locks the checkout to exactly this value, overriding the SPV's min/max purchase range. Omit to let the recipient choose an amount within that range. example: 5000000 success_url: type: string nullable: true description: Where to send the recipient after they complete the session. example: https://partner.com/done expires_in_hours: type: integer nullable: true minimum: 1 maximum: 2160 description: Session lifetime in hours (default 720, i.e. 30 days). example: 72 metadata: type: object nullable: true description: Free-form key/value pairs stored with the session and echoed back on reads. intent_id: type: string nullable: true description: The Intent that gated the SPV's creation, for audit; stored in `metadata`. example: int_abc123 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 description: The fixed amount the session locks to, in cents; null when the recipient chooses their own amount. 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 metadata: type: object description: The key/value pairs supplied at creation. created_at: type: string format: date-time example: '2025-01-15T10:00:00Z' expires_at: type: string format: date-time nullable: true events: type: array description: Durable platform events recorded about this session, oldest first. Only present on the single-session GET. items: type: object properties: id: type: string example: evt_abc123 event: type: string example: investment_session.completed occurred_at: type: string format: date-time 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'