# 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 ## Versioning The API is versioned by the dated `Wefunder-Version` request header (`YYYY-MM-DD`), never by the URL. Resolution is header → your application's pinned version → latest; every response carries `Wefunder-Version` (the version that handled it) and `Wefunder-Version-Latest`. An unknown value returns `400 invalid_request` naming the valid versions. Current: `2026-10-01`. ## 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` (no version in the path). version: '2.0' contact: name: Wefunder API Support email: api@wefunder.com servers: - url: https://api.wefunder.com description: One host for live and sandbox; the credential prefix (pk_live_ / pk_test_) selects the environment. There is currently one API version and no version appears in the path. 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_...`). **Public view (`read:public`).** Reachable with either a server-side (client_credentials) token or a user access token. 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 for a logged-out visitor. **Logged-in view (`read:explore`).** A user access token carrying `read:explore` gets what *that user* sees on wefunder.com/explore: the public offerings plus any the user qualifies for (accredited-only / Vault deals, invited private rounds), each checked against the same visibility policy the website applies. The response is a strict superset of the public one — same fields and ids — with two extra attributes, `publicly_visible` and `invested`, and `meta.personalized: true`. A server-side key can never hold `read:explore`; a token with `read:explore` but no resolvable user gets the public view. Order with `sort` (default: most raised); paginate with `cursor`. `meta.total_count` is the number of offerings across all pages, after filters. **Filters** are objective, per-offering terms — exemption family, security type, TTW vs. live, minimum investment ceiling, close date, amount raised floor. The response echoes the applied set in `meta.filters`. An out-of-vocabulary value is a `400` naming the accepted values. There are deliberately no relevance, quality, or ranking criteria. Use this endpoint to: - Discover and monitor active deals - Build deal aggregators and public listings operationId: listOfferings security: - bearerAuth: - read:public - bearerAuth: - read:explore 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 - name: exemption in: query description: Only offerings under this SEC exemption family. Reg CF and Reg D are the supported public filters. schema: type: string enum: - reg_cf - reg_d example: reg_cf - name: security in: query description: Only offerings issuing this kind of security — the `security.type` value on results. schema: type: string enum: - safe - equity - convertible_note - revenue_share - debt - fund - other example: safe - name: testing_the_waters in: query description: '`false` — only live rounds accepting investments; `true` — only Testing-the-Waters rounds collecting non-binding reservations.' schema: type: boolean example: false - name: max_min_investment in: query description: Only offerings whose minimum investment is at most this amount (USD). schema: type: number example: 100 - name: closing_before in: query description: Only offerings whose funding close date is on or before this date (ISO 8601). Offerings with no close date are excluded. schema: type: string format: date example: '2026-12-31' - name: min_amount_raised in: query description: Only offerings that have raised at least this amount so far (USD). schema: type: number example: 250000 - name: industry in: query description: | Only companies tagged with any of these industries — the curated tags the wefunder.com explore page filters by (see each offering's `industries`). One value or a comma-separated list; unknown values are a 400 listing the vocabulary. Objective facets, not a ranking. style: form explode: false schema: type: array items: type: string enum: - ar_and_vr - agriculture_and_agtech - artificial_intelligence - biotech - blockchain_and_web3 - edtech - energy - fintech_and_finance - food_tech - games - health_and_fitness - healthcare - manufacturing - mobile_apps - robotics - science_and_r_and_d - transportation - travel_and_tourism - bars_and_clubs - brewery - cafe_and_food_truck - events_and_festivals - education - distillery_and_vineyards - fashion - film - media - music - restaurant - sports - real_estate - pets - recreation - infrastructure_and_construction - childcare - sustainability - sports_tech - home_tech - devtools - clean_tech - beauty - entertainment - technology - brick_and_mortar - food_and_beverage - climate_change - alcohol_and_vice - consumer_goods - hardware - moonshots example: robotics,hardware - name: business_model in: query description: Only companies with any of these business models (see each offering's `business_models`). One value or a comma-separated list. style: form explode: false schema: type: array items: type: string enum: - b2b - b2c - subscription - saas - service - marketplace - ecommerce - retail example: b2b - name: followed in: query description: | **Logged-in view only** (`read:explore` with a user). `true` — only companies the authorizing user follows on wefunder.com (their watchlist); `false` — only companies they do not follow. A 400 on the public view, which has no user to filter by. schema: type: boolean example: true responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/OfferingListEnvelope' '400': description: A filter value is out of vocabulary or malformed; `error.message` names the accepted values. '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 offering by its id (`ofr_...`). With `read:public`, 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. With `read:explore` on a user access token, the offering resolves if *that user* may see it on wefunder.com (e.g. an accredited-only or invited round), and the response gains `publicly_visible` and `invested`. operationId: getOffering security: - bearerAuth: - read:public - bearerAuth: - read:explore 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?.status); /companies/{id}/disclosures: get: tags: - Explore summary: Get a company's public Form C disclosures description: | The Details tab of the company's Wefunder page as structured data — the company's own Form C disclosures, section for section: financial statements for the fiscal years on file, the page's ratios, the founder's current-position disclosure (cash on hand, monthly revenue / costs / burn), the financial-condition narrative, document links, risks, use of funds, directors and officers, 20%+ holders, capital structure, prior offerings, outstanding notes and debts, and related-party transactions. Same "who has a page" rule as `GET /companies/{id}`. The round read is the page's own: the display round for the viewer, swapped for its concurrent Reg CF sibling on a Reg D round, and only when it is a Reg CF / ECSP round with a Form C. A company with no such round has no details tab on the site and answers `404` with `error.type: no_disclosures` — distinct from an unknown or hidden company. A section the company hides on the site is `null`. Money is USD as decimal strings; ratios are percentages as decimal strings. Everything here is the issuer's own disclosure, served as filed, not a Wefunder assessment. operationId: getCompanyDisclosures security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: id in: path required: true schema: type: string example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY - name: sections in: query description: | Only these sections (`offering_id` and `filing` are always included). Omit for the whole tab; a disclosure-heavy company runs to ~100 KB. Unknown names are a 400 listing the vocabulary. style: form explode: false schema: type: array items: type: string enum: - business - financial_statements - ratios - current_position - financial_condition - financial_statement_documents - investment_documents - risks - use_of_funds - directors - officers - voting_power - capital_structure - prior_offerings - outstanding_notes - outstanding_debts - related_parties example: financial_statements,ratios,current_position responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanyDisclosuresEnvelope' '400': description: '`sections` names a section that does not exist.' '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown or hidden company (`error.type` = `not_found`), or no public Form C disclosures (`error.type` = `no_disclosures`). '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: disclosures } = await wf.unwrap( wf.raw.getCompanyDisclosures({ path: { id: "co_abc123Example" }, query: { sections: ["business", "risks", "use_of_funds"] }, }), ); console.log(disclosures?.attributes); /companies/{id}/pitch: get: tags: - Explore summary: Get a company's pitch description: | The Overview tab's pitch as structured data: the story as ordered blocks (`heading`, `paragraph`, `list`, `image`, `video`, `footnote`), plus the perk tiers of the round the page shows the viewer. Images and embedded videos keep their place in the document with an absolute URL, because founders publish perk tiers, stretch goals, timelines, and charts as pictures (typically with no alt text) and a plain-text rendering would drop them. What is parsed is what the page renders: the story passes through the site's own sanitizer first, so an embed the page refuses (a host off its allowlist, a `javascript:` URL) is never served here either. Same "who has a page" rule and round chooser as `GET /companies/{id}`. A site-public company with no round the viewer may see still has a pitch (`perks: null`). `perks.described_in_pitch` is a heuristic: true when the structured perk list is a placeholder ("See investor overview page", or one identical sentence on every tier) and the real tiers are in the story's images. `authored_by` says who wrote the story: `company` (the issuer's own words and imagery, not a Wefunder assessment) or `wefunder` (a Wefunder-prepared deal memo the company did not participate in; `disclaimer` carries the page's notice). A pitch can run to ~70 KB; use `sections` to ask for less. operationId: getCompanyPitch security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: id in: path required: true schema: type: string example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY - name: sections in: query description: | Only these sections (`offering_id`, `title`, `url`, `authored_by`, and `disclaimer` are always included). Omit for the whole pitch. Unknown names are a 400 listing the vocabulary. style: form explode: false schema: type: array items: type: string enum: - story - perks example: perks responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanyPitchEnvelope' '400': description: '`sections` names a section that does not exist.' '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown or hidden company. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: pitch } = await wf.unwrap( wf.raw.getCompanyPitch({ path: { id: "co_abc123Example" }, query: { sections: ["story", "perks"] } }), ); console.log(pitch?.attributes); /companies/{id}/updates: get: tags: - Explore summary: List a company's posts description: | The company page's Posts tab as data: the published updates, notes, and spotlights the viewer may see, pinned first then newest, with a plain-text excerpt each. With `read:explore` the viewer is the authorizing user, so a follower or investor sees the community- and investors-only posts the site shows them; with `read:public` it is the anonymous view. Same query as the tab (`FeedItem.viewable_by`); investments, Q&A threads, and investor quotes belong to other tabs and are not here. Fetch one post in full with `GET /companies/{id}/updates/{update_id}`. Posts are the issuer's own words. operationId: listCompanyUpdates security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: id in: path required: true schema: type: string example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY - name: cursor in: query schema: type: string - name: per_page in: query schema: type: integer default: 20 maximum: 50 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanyUpdateListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown company, or one the site does not show this viewer. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const page = await wf.unwrap(wf.raw.listCompanyUpdates({ path: { id: "co_abc123Example" }, query: { per_page: 10 } })); for (const update of page.data ?? []) console.log(update.id, update.attributes?.title); console.log(page.meta?.next_cursor); /companies/{id}/updates/{update_id}: get: tags: - Explore summary: Get one company post in full description: One post with its full plain-text content, by the id the list returned. Same visibility as the list; a post the tab would not show this viewer is a 404. operationId: getCompanyUpdate security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: id in: path required: true schema: type: string - name: update_id in: path required: true schema: type: string responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanyUpdateEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown company or post, or one the site does not show this viewer. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: update } = await wf.unwrap( wf.raw.getCompanyUpdate({ path: { id: "co_abc123Example", update_id: "upd_9t2xExample" } }), ); console.log(update?.attributes?.title); /companies/{id}/questions: get: tags: - Explore summary: List a company's investor questions and answers description: | The company page's Ask tab as data: the questions investors asked and the answers, as the tab lists them for this viewer (the same repository and visibility as the site). Like the tab, questions default to the current raise — those asked since it opened (`meta.questions_since`); `past_raises=true` includes every raise, which the site offers to any visitor because past-raise questions are public. `sort` is the tab's dropdown: `relevance` (default: highlighted, then answered, then most liked), `recent`, `upvoted`, `unanswered`. Text is plain. Questions and answers carry their id (`cmt_...`); the integer key never leaves the API. Answers are the founders' own words. `q` searches question and answer text (Postgres full text: quoted phrases and `-word` work; one- or two-word queries also prefix-match) and replaces the sort with match rank — `meta.sort` is then `search` and each result's `match` says whether its question or an answer matched. `unanswered_by_team=true` keeps only questions with no live answer from the company's team (the founder's queue; the `unanswered` sort merely orders by whether anyone replied). operationId: listCompanyQuestions security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: id in: path required: true schema: type: string example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY - name: sort in: query schema: type: string enum: - relevance - recent - upvoted - unanswered default: relevance - name: past_raises in: query schema: type: boolean default: false - name: q in: query description: Full-text search over questions and answers. At most 200 bytes. schema: type: string maxLength: 200 - name: unanswered_by_team in: query schema: type: boolean default: false - name: cursor in: query schema: type: string - name: per_page in: query schema: type: integer default: 20 maximum: 50 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanyQuestionListEnvelope' '400': description: '`sort` is not one of the tab''s options, or `q` is over 200 bytes.' '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown company, or one the site does not show this viewer. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const page = await wf.unwrap( wf.raw.listCompanyQuestions({ path: { id: "co_abc123Example" }, query: { q: "revenue", unanswered_by_team: true, per_page: 20 }, }), ); for (const question of page.data ?? []) console.log(question.id, question.attributes); /companies/search: get: tags: - Companies summary: Search companies by name description: | The wefunder.com search bar, companies only. Same Algolia request, same result ordering, and same page size as the site's top bar, so the results are what a visitor typing the same text would see, in that order — companies raising now and companies funded in the past, whether or not they appear on `/explore`. The order is the site's search order (match quality first, then the site's tie-breaks); nothing here recommends. Each result carries the company's `co_` id for `GET /companies/{id}`. With `read:explore` the accreditation gate matches the authorizing user (an accredited viewer sees the accredited-only companies the site would show them); with `read:public` it is the anonymous view. operationId: searchCompanies security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: q in: query required: true description: The company name, or part of it. At most 200 characters and 500 bytes. schema: type: string maxLength: 200 example: acme - name: limit in: query description: Maximum results, 1–20. Default 8, the same as the site. schema: type: integer minimum: 1 maximum: 20 default: 8 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanySearchResultListEnvelope' '400': description: '`q` is missing, blank, or over 200 characters / 500 bytes.' '401': $ref: '#/components/responses/Unauthorized' '403': description: The token holds neither `read:public` nor `read:explore`. '429': $ref: '#/components/responses/RateLimitExceeded' '503': description: The search provider failed; retry. Not "no results". x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const results = await wf.unwrap(wf.raw.searchCompanies({ query: { q: "solar", limit: 5 } })); for (const hit of results.data ?? []) console.log(hit.id, hit.attributes?.name); /companies/{id}: get: tags: - Explore summary: Get a company page description: | The company page behind an offering, as structured data — the "click a company" step after `/explore`. Addressed by the `co_...` id every offering carries in `company.id`. Keeps apart the three raised numbers the page shows and readers conflate: `current_raise.amount_raised` (this campaign's live rounds combined, on Wefunder), `past_rounds[]` (prior rounds the page lists, each tagged `source: wefunder` for rounds observed here or `reported` for founder-disclosed off-platform rounds), and `totals.profile_total_raised` (the number on the page's ticker bar). Every figure comes from the same policy the page uses, so the API never disagrees with the site. **Who has a page.** Every company whose profile wefunder.com lists publicly — the same set the site's search bar returns (published, not invite-only, searchable or with a live Form C; accredited-only companies need an accredited viewer) — plus the `/explore` set. A funded company, or one the viewer may see no round of, renders with `raising: false` and `current_raise: null`; its history is in `wefunder_rounds`. **Logged-in view (`read:explore`)** additionally covers the vault / syndicate-led companies the site shows an accredited viewer, and the round described is the one the page would show that viewer. A valid id for a company outside the viewer's set is a `404`, never a partial payload. operationId: getCompany security: - bearerAuth: - read:public - bearerAuth: - read:explore parameters: - name: id in: path required: true schema: type: string description: The company's id (`co_...`). example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CompanyEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown id, or a company the viewer may not see. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: company } = await wf.unwrap(wf.raw.getCompany({ path: { id: "co_abc123Example" } })); console.log(company?.attributes?.name); /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); /users/me/companies: get: tags: - Users summary: List the companies you edit description: | The companies the authenticated user is a founder or team member of — precisely the companies the founder-scoped endpoints under `/companies/{company_id}/...` (dashboard, investors, fundraises) will accept. Anyone else's company returns 403 there, so call this first to learn which `co_` ids you hold. Not paginated. `raising` is true when the company has a round currently accepting investments or reservations. `roles` are the user's roles on the company (`founder`, `employee`, ...). operationId: listMyCompanies security: - bearerAuth: - read:companies responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/MyCompanyListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const companies = await wf.unwrap(wf.raw.listMyCompanies()); for (const company of companies.data ?? []) console.log(company.id, company.attributes?.name); /users/me/follows: get: tags: - Users summary: List the companies you follow description: | The authenticated user's watchlist — the companies they follow on wefunder.com (the same rows the site's Follow button writes), newest first. Each entry is in the search-result shape (`co_` id, name, tagline, URL, logo, `raising`, `profile_available`) plus `followed_at`. The list owner sees every company they follow. operationId: listFollowedCompanies security: - bearerAuth: - read:follows parameters: - name: cursor in: query schema: type: string - name: per_page in: query schema: type: integer default: 25 maximum: 100 responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/FollowedCompanyListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const page = await wf.unwrap(wf.raw.listFollowedCompanies({ query: { per_page: 25 } })); for (const company of page.data ?? []) console.log(company.id); /users/me/follows/{company_id}: put: tags: - Users summary: Follow a company description: | Follow the company for the authenticated user — exactly what pressing Follow on its Wefunder page does, side effects included (the company counts the user as a follower and may be notified; the user receives the company's updates). Idempotent: following an already-followed company returns `changed: false`. `404` for a company the site would not show this user. Requires `write:follows`, which the user grants explicitly; it is never implied by `read:mcp`. operationId: followCompany security: - bearerAuth: - write:follows parameters: - name: company_id in: path required: true schema: type: string example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY responses: '200': description: The follow state after the call. content: application/json: schema: $ref: '#/components/schemas/FollowStateEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: The token does not hold `write:follows`. '404': description: Unknown company, or one the site does not show this user. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const result = await wf.unwrap(wf.raw.followCompany({ path: { company_id: "co_abc123Example" } })); console.log(result); delete: tags: - Users summary: Unfollow a company description: 'Unfollow the company for the authenticated user. Idempotent (`changed` is false when they were not following it). No visibility gate: a follow you hold is always undoable, even for a company since hidden. Requires `write:follows`.' operationId: unfollowCompany security: - bearerAuth: - write:follows parameters: - name: company_id in: path required: true schema: type: string example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY responses: '200': description: The follow state after the call. content: application/json: schema: $ref: '#/components/schemas/FollowStateEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: The token does not hold `write:follows`. '404': description: Unknown company. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const result = await wf.unwrap(wf.raw.unfollowCompany({ path: { company_id: "co_abc123Example" } })); console.log(result); /investments: get: tags: - Investments summary: List or sync investments description: | The investment records the token may see, served from **published** state (docs: *Investments: list, sync, retrieve*). The audience is the union of the companies the token's user edits (their founder-visible records) and the user's own investments (every applied record of theirs, whatever its status). Filters narrow within that audience; they never widen it. Three ways to call it: - **List** (no `cursor`, no `updated_since`): the current records, tombstones excluded, ordered by investment. Page with `meta.next_cursor` while `meta.has_more`; the final page's cursor is a sync cursor positioned at the moment the listing started. - **Sync by cursor**: only the records whose published state changed since the cursor, one record per investment, tombstones (`visible: false`, only `id`) included. Apply in `cursor` order; always store `meta.next_cursor`. - **Sync by time**: `updated_since=` is sugar for a cursor at the ledger position just before that time (with a 60 s safety margin). The response is a normal sync page, so continue with its `meta.next_cursor`. Records are eventually consistent (about ten minutes on the common paths); each carries `observed_at`. `GET /investments/{id}` returns the same shape derived now. Investor identity fields on other investors' records require `read:investors:pii`; a user always sees their own. Money is integer minor units plus `amounts.currency`. **410 Gone** means the cursor or `updated_since` predates the 90-day retention window: list again and replace your dataset. `status` cannot be combined with sync (a record leaving the status would never be delivered). operationId: listInvestments security: - bearerAuth: - read:investments parameters: - name: company_id in: query required: false description: Company external id (`co_…`). schema: type: string - name: offering_id in: query required: false description: | Offering external id (`ofr_…`). In sync mode an investment that moved to another offering is delivered once more as a tombstone under this filter. schema: type: string - name: investor_id in: query required: false description: Investor external id (`usr_…`). schema: type: string - name: status in: query required: false description: List mode only. schema: type: string enum: - reserved - active - executed - canceled - converted - name: updated_since in: query required: false description: ISO 8601 timestamp; switches to sync mode. Mutually exclusive with `cursor` (cursor wins). schema: type: string format: date-time - name: cursor in: query required: false description: Opaque cursor from a previous response's `meta.next_cursor`. schema: type: string - name: per_page in: query required: false description: Records per page, 1–100 (default 25). Sync pages count distinct investments. schema: type: integer minimum: 1 maximum: 100 default: 25 responses: '200': description: A page of records. content: application/json: schema: $ref: '#/components/schemas/InvestmentListEnvelope' '400': description: Malformed cursor or `updated_since`, unknown `status`, or `status` combined with sync. '401': $ref: '#/components/responses/Unauthorized' '404': description: Unknown `company_id`, `offering_id` or `investor_id`. '410': description: | The cursor or `updated_since` is older than the retention window. List again (no cursor) and replace your stored records from the result. '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); /investments/{id}: get: tags: - Investments summary: Retrieve an investment (current) description: | The same record shape as the list, derived **now** from the investment rather than read from published state, so an investor who just acted sees the change at once. Available for any investment in the token's audience: the user's own (any status) or a founder-visible investment on a company the user edits. `meta.published_matches` says whether the published record already agrees; the sync stream converges within its interval. operationId: getInvestment security: - bearerAuth: - read:investments parameters: - name: id in: path required: true description: Investment external id (`inv_…`). schema: type: string responses: '200': description: The current record. content: application/json: schema: $ref: '#/components/schemas/InvestmentEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': description: Not found, not in the token's audience, or not visible to it. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const investment = await wf.investments.get("inv_8f3kQ2Example"); console.log(investment.status, investment.amounts?.committed_cents, investment.investor?.id); /offerings/{offering_id}/stats: get: tags: - Investments summary: Investment totals for an offering description: | Count and committed amount of the founder-visible published records for one offering, by status, from the same rows `GET /investments?offering_id=…` lists, so the two reconcile. These are **not** the public campaign figures (the progress bar has its own rules and sources); they are the sum of the records you can list. Requires the token's user to edit the offering's company. operationId: getOfferingStats security: - bearerAuth: - read:investments parameters: - name: offering_id in: path required: true description: Offering external id (`ofr_…`). schema: type: string responses: '200': description: Totals by status. content: application/json: schema: $ref: '#/components/schemas/OfferingStatsEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: The token's user does not edit the offering's company. '404': description: Unknown offering. '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: stats } = await wf.unwrap( wf.raw.getOfferingStats({ path: { offering_id: "ofr_QfmTyP8qjfYAvkEgREyL3kLf" } }), ); console.log(stats); /companies/{company_id}/investments/changes: get: tags: - Investment Delta summary: Bootstrap or page investment changes for a company description: | The Investment Delta API: keep a copy of a company's investment records in sync by polling. Call without `cursor` to **bootstrap** (list every current record for the company); every response carries `meta.next_cursor`; call again with that cursor to receive only the records whose published state changed since. Records are the same shape in both modes. - Records are **published state**, not live rows: Wefunder publishes a record when the investment's founder-visible state changes, and serves exactly what was published. - `visible: false` records are **tombstones**: the investment is no longer visible to the company (canceled, converted to another round, hidden, or deleted). Delete your copy. - Apply records in `cursor` order. `observed_at` is when the payload was observed, not a version; a record you read late carries the *current* published state. - **410 Gone** means your cursor predates the 90-day retention window. Bootstrap again and **replace** your dataset for the company from the result (records absent from the bootstrap are deleted). - Investor identity fields (`investor.name`, `investor.legal_name`, `investor.email`, `investor.address`, `investor.bio`, `message`, `external_username`) are **omitted** unless the token carries `read:investors:pii`. - Money is integer minor units plus `amounts.currency` (`committed_cents`, `investment_size_cents`, `in_escrow_cents`, `contracts[].override_amount_cents`). `shares` and `average_share_price` are decimal strings, `null` on rounds without shares. `amounts.raised_cents` is the row's contribution to the public raised figure (0 while `needs_whitelisting`); sum it, not `committed_cents`, to match the deal page. operationId: listCompanyInvestmentChanges security: - bearerAuth: - read:investments - bearerAuth: - read:companies parameters: - name: company_id in: path required: true description: The company's external id (`co_…`). schema: type: string - name: cursor in: query required: false description: | Opaque cursor from a previous response's `meta.next_cursor`. Omit to bootstrap. schema: type: string - name: per_page in: query required: false description: Records per page, 1–100 (default 25). Delta pages count distinct investments. schema: type: integer minimum: 1 maximum: 100 default: 25 responses: '200': description: A page of records. content: application/json: schema: $ref: '#/components/schemas/InvestmentChangeListEnvelope' '400': description: Malformed cursor. '401': $ref: '#/components/responses/Unauthorized' '403': description: Token lacks both `read:investments` and `read:companies`, or the user cannot edit this company. '410': description: | The cursor is older than the retention window. Bootstrap again (no cursor) and replace your stored records for this company. '429': $ref: '#/components/responses/RateLimitExceeded' /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const summary = await wf.portfolio.get(); console.log( summary.attributes?.total_current_value_cents, summary.attributes?.return_multiple, ); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- for await (const position of wf.portfolio.positions.all({ status: "active" })) { console.log( position.id, position.attributes?.company?.name, position.attributes?.current_value_cents, ); } /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: Opaque cursor from the previous response's `meta.next_cursor`. - name: per_page in: query schema: type: integer default: 25 maximum: 100 description: Results per page, 1-100. Defaults to 25. 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: - newest - oldest - alphabetical - last_activity - investment_amount description: | Sort order. `investment_amount` (highest total invested first) is **moderator-only**; non-moderators requesting it get the default order. Unknown values fall back to the default. - 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' - 'false' description: '**Moderator-only.** Filter by accreditation status; ignored for non-moderators.' - name: tag in: query schema: type: string description: Filter by 'can help with' tag (ILIKE match) - name: location in: query schema: type: string description: Match on city, state, or country (public location data only). - name: cursor in: query schema: type: string description: Opaque cursor from the previous response's `meta.next_cursor`. - name: per_page in: query schema: type: integer default: 25 maximum: 100 description: Results per page, 1-100. Defaults to 25. 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: integer minimum: 1 maximum: 100 example: 20 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 /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 description: Member ids as returned by the list endpoint (`mem_…`), in the desired display order. Unknown ids return 404 and change nothing. example: - mem_8Kd0aB3xQ9k2 - mem_vF8mNp1zT5wY - mem_2Lq7cR4sX0bE 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 the syndicate's **live** deals (open, oversubscribed, or closing) with status, terms, and metrics. Closed and upcoming rounds are not listed; `statistics.total_deals` counts all linked rounds. operationId: listSyndicateDeals security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - name: cursor in: query schema: type: string description: Opaque cursor from the previous response's `meta.next_cursor`. - name: per_page in: query schema: type: integer default: 25 maximum: 100 description: Results per page, 1-100. Defaults to 25. 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). `amount` is whole dollars as a string (e.g. `"5000"` is $5,000). Offset-paginated: pass `offset` and `per_page`; `meta` carries `total_count`, `offset`, `per_page`, and `has_more`. Without `per_page` the full list is returned. operationId: listSyndicateDealInvestors security: - bearerAuth: - read:syndicates parameters: - $ref: '#/components/parameters/syndicate_id' - $ref: '#/components/parameters/fundraise_id' - name: offset in: query schema: type: integer default: 0 description: Number of investors to skip. - name: per_page in: query schema: type: integer maximum: 200 description: Results per page, 1-200. Omit to return every investor. 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). **Moderator-only.** `amount` and `meta.total_amount` are whole dollars as strings. 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. `total_raised` is whole dollars as a string. 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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: summary } = await wf.unwrap( wf.raw.getSyndicatePortfolio({ path: { syndicate_id: "syn_example" }, }), ); console.log(summary?.attributes?.total_current_value_cents); /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' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const page = await wf.unwrap( wf.raw.listSyndicatePortfolioPositions({ path: { syndicate_id: "syn_example" }, query: { status: "active", per_page: 25 }, }), ); for (const position of page.data ?? []) { console.log(position.id, position.attributes?.investor_count); } /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/preview: post: tags: - Intents summary: Preview an intent without proposing it description: | Runs every check `POST /intents` runs — the action's feature flag, resource type and id, token binding, who may propose, and the handler's own validation — and returns what the reviewer would read, without minting anything. The draft step for agents: admitted by any proposing scope **or** `read:explore`, and the action's own scope is not required, so a user can shape the text before granting the write permission. Same request body as `POST /intents` (without `idempotency_key`). Errors are the same as `POST /intents` would give. operationId: previewIntent security: - bearerAuth: - read:explore - bearerAuth: - write:syndicates - bearerAuth: - write:comments requestBody: required: true content: application/json: schema: type: object required: - action_name - resource_type - resource_id properties: action_name: type: string resource_type: type: string resource_id: type: string params: type: object responses: '200': description: The proposal would be accepted. content: application/json: schema: $ref: '#/components/schemas/IntentPreviewEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: The handler refused it; `error.message` is the reason. content: application/json: schema: $ref: '#/components/schemas/Error' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const preview = await wf.unwrap( wf.raw.previewIntent({ body: { action_name: "syndicates.close_deal", resource_type: "Club", resource_id: "syn_example", params: { fundraise_id: 99 }, }, }), ); console.log(preview); /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. Each action names the scope that may propose it and the resource type it targets (`syndicates.*` → `write:syndicates` on a syndicate; `comments.create` → `write:comments` on a `Company`). The token must hold that action's scope; holding another proposing scope is not enough (403 `insufficient_scope`). `comments.create` posts a question on a company's Ask tab, or an answer to one, as the requesting user once they approve on wefunder.com. `resource_id` is the company (`co_...`); `params` is `{ target_type: "company" | "comment", target: "co_..." | "cmt_...", body, disclosure_key? }` (`disclosure_key` ∈ investor, stockholder, promoter, financial_stakeholder). Who may post is the site's own rule: anyone the page is available to may ask (unless the company has closed questions); only the company's team may answer. Only the requester can approve. 20 proposals per user per day (429). An `idempotency_key` names one operation. Reusing it for the same operation returns the existing intent (200) while that intent is pending, approved, executing, or executed; reusing it for a different action, resource, or params is a 409 `idempotency_conflict`. An action still rolling out per account returns 403 `feature_disabled` for a user who is not yet enabled. `POST /intents/preview` runs the same checks without minting. See [Intents documentation](/concepts/intents) for the full pattern. operationId: createIntent security: - bearerAuth: - write:syndicates - bearerAuth: - write:comments 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 - comments.create resource_type: type: string example: Club description: The resource type the action targets, as the action table names it; the example is the syndicate type. resource_id: type: string description: The resource's id in the API's vocabulary — the syndicate's `syn_...` for a `syndicates.*` action. example: syn_aB3xQ9k2vF8mNp1zT5wY7Qc4 params: type: object description: Action-specific parameters example: fundraise_id: 99 idempotency_key: type: string description: Client-provided key naming this one operation. While an intent with this key is pending, approved, executing, or executed, the same operation returns it (200); a different operation under the same key is a 409. 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: '200': description: Idempotent replay — the existing intent for this key and operation. content: application/json: schema: $ref: '#/components/schemas/IntentEnvelope' '201': description: Intent created content: application/json: schema: $ref: '#/components/schemas/IntentEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: '`idempotency_conflict` — the key was already used for a different operation. Nothing was minted.' content: application/json: schema: $ref: '#/components/schemas/Error' '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: "syn_example", 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 description: Opaque cursor from the previous response's `meta.next_cursor`. - name: per_page in: query schema: type: integer default: 25 maximum: 100 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: The intent's id (`int_...`), as returned when the intent was minted (`data.id` / `meta.*_intent.id`). The legacy UUID form is still accepted. example: int_aB3xQ9k2vF8mNp1zT5wY7Qc4 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 description: Opaque cursor from the previous response's `meta.next_cursor`. - name: per_page 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); } } /installations: get: tags: - Installations summary: List installations of your app description: | Where the calling application is installed. Someone who manages the app's organization sees every install; anyone else sees the installs they made or could revoke. Revoked installs stay listed for 30 days. operationId: listInstallations security: - bearerAuth: - read:installations responses: '200': description: Installations of the calling application content: application/json: schema: $ref: '#/components/schemas/InstallationListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const installs = await wf.unwrap(wf.raw.listInstallations()); for (const install of installs.data ?? []) console.log(install.id, install.attributes); post: tags: - Installations summary: Install your app on a company or syndicate description: | Installs the calling application on a company or syndicate the token's user can edit (or, for a syndicate, manages), and mints the **company-owned token** the install stands for. The token appears in this response only. Requires a token authorized by a user. The install is granted at the caller's tier (founder, admin or editor for a company). An editor-tier install carries read scopes only; write scopes in `scopes` are dropped. Installing on a company outside your own organization requires the app to be approved. operationId: createInstallation security: - bearerAuth: - write:installations requestBody: required: true content: application/json: schema: type: object required: - target_type - target_id properties: target_type: type: string enum: - company - syndicate target_id: type: string description: The company (`co_…`) or syndicate (`syn_…`) external id. scopes: type: array items: type: string description: Subset of the app's declared scopes to grant. Defaults to all of them. tier: type: string description: Optional higher tier (`founder` / `full_access`), honored only when the caller holds it. responses: '201': description: The new installation and its token (shown once) content: application/json: schema: $ref: '#/components/schemas/InstallationTokenEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: Already installed here; `error.details.installation` is the existing install's id. '422': description: Unknown `target_type`, or none of the requested scopes can be granted by the caller's role. x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const install = await wf.unwrap( wf.raw.createInstallation({ body: { target_type: "company", target_id: "co_abc123Example", scopes: ["read:offerings", "read:investments"] }, }), ); console.log(install.data?.id, install.data?.attributes); /installations/eligible: get: tags: - Installations summary: List where you can install your app description: | Companies the token's user can edit and syndicates they manage, with the tier an install would be granted at and the current install if one exists. Requires a token authorized by a user. operationId: listEligibleInstallTargets security: - bearerAuth: - read:installations parameters: - name: target_type in: query schema: type: string enum: - company - syndicate responses: '200': description: Eligible install targets content: application/json: schema: $ref: '#/components/schemas/EligibleTargetListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const targets = await wf.unwrap(wf.raw.listEligibleInstallTargets({ query: { target_type: "company" } })); for (const target of targets.data ?? []) console.log(target); /installations/{external_id}: parameters: - name: external_id in: path required: true schema: type: string description: Installation external id (`inst_…`). get: tags: - Installations summary: Retrieve an installation operationId: getInstallation security: - bearerAuth: - read:installations responses: '200': description: The installation content: application/json: schema: $ref: '#/components/schemas/InstallationEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: install } = await wf.unwrap(wf.raw.getInstallation({ path: { external_id: "ins_9t2xExample" } })); console.log(install?.attributes); delete: tags: - Installations summary: Revoke an installation description: | Revokes the install and every token issued for it. Requires a token authorized by a user who can edit the company (or manages the syndicate), or who administers the app's organization. operationId: revokeInstallation security: - bearerAuth: - write:installations responses: '200': description: The installation, now revoked content: application/json: schema: $ref: '#/components/schemas/InstallationEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const result = await wf.unwrap(wf.raw.revokeInstallation({ path: { external_id: "ins_9t2xExample" } })); console.log(result); /installations/{external_id}/tokens: post: tags: - Installations summary: Mint a token for an installation description: | Mints another company-owned token for an active install — for example one a founder made through the install link. Scopes default to the install's grant and can only narrow it. The token appears in this response only. Requires a token authorized by a user who installed it, could revoke it, or manages the app's organization. operationId: createInstallationToken security: - bearerAuth: - write:installations parameters: - name: external_id in: path required: true schema: type: string requestBody: required: false content: application/json: schema: type: object properties: scopes: type: array items: type: string responses: '201': description: The installation and a new token (shown once) content: application/json: schema: $ref: '#/components/schemas/InstallationTokenEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: The installation has been revoked. x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const token = await wf.unwrap( wf.raw.createInstallationToken({ path: { external_id: "ins_9t2xExample" }, body: { scopes: ["read:investments"] } }), ); console.log(token.data); /webhook_endpoints: get: tags: - Webhook Endpoints summary: List webhook endpoints description: | Your application's webhook endpoints. The signing secret is never included. Requires an owner, admin, or developer role in the application's organization. operationId: listWebhookEndpoints security: - bearerAuth: - read:webhooks responses: '200': description: Endpoints for the calling application content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointListEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const { data: endpoints, meta } = await wf.webhookEndpoints.list(); console.log(`${endpoints?.length} of ${meta?.quota} endpoint slots used`); for (const ep of endpoints ?? []) { console.log(ep.id, ep.attributes?.url, ep.attributes?.enabled, ep.attributes?.last_delivery_status); } post: tags: - Webhook Endpoints summary: Create a webhook endpoint description: | Registers an endpoint for your application. The response is the only time the signing `secret` is returned; store it to verify signatures. Requires an owner, admin, or developer role in the application's organization. On the sandbox API this returns `403 manage_endpoints_on_live_api`: create test endpoints through the live API with `mode: "test"`, and they are mirrored into sandbox. operationId: createWebhookEndpoint security: - bearerAuth: - write:webhooks requestBody: required: true content: application/json: schema: type: object required: - url - events properties: url: type: string format: uri description: Public HTTPS URL to receive deliveries (private/internal IPs rejected). example: https://yourapp.com/webhooks/wefunder events: type: array minItems: 1 description: Event names to subscribe to (from the event catalog). At least one. items: type: string enum: - investment.created - investment.reinstated - investment.canceled - investment.converted - investment.amount_changed - investment.executed - offering.opened - offering.closing - offering.closed - offering.canceled - investment_session.created - investment_session.started - investment_session.completed - investment_session.expired - investment_session.canceled - investment.changed - syndicate_member.invited - syndicate_member.reinvited - syndicate_member.applied - syndicate_member.approved - syndicate_member.joined example: - offering.opened - investment.executed mode: type: string enum: - live - test default: live responses: '201': description: Endpoint created (includes the one-time secret) content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': description: Validation error (bad URL, unknown or missing events, invalid mode, or quota reached) content: application/json: schema: $ref: '#/components/schemas/Error' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const endpoint = await wf.webhookEndpoints.create({ url: "https://yourapp.com/webhooks/wefunder", // public HTTPS (localhost/private IPs rejected) events: ["offering.opened", "investment.executed", "investment.changed"], mode: "live", }); // Store this now — it's needed by constructEvent() and is never shown again. const signingSecret = endpoint.attributes?.secret; console.log(endpoint.id, signingSecret); /webhook_endpoints/{external_id}: get: tags: - Webhook Endpoints summary: Get a webhook endpoint description: Requires an owner, admin, or developer role in the application's organization. operationId: getWebhookEndpoint security: - bearerAuth: - read:webhooks parameters: - $ref: '#/components/parameters/webhook_external_id' responses: '200': description: The endpoint (secret omitted) content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const endpoint = await wf.webhookEndpoints.get("whe_8f3ExampleEndpoint00"); console.log(endpoint.attributes?.events, endpoint.attributes?.failing_since); patch: tags: - Webhook Endpoints summary: Update a webhook endpoint description: | `events` replaces the subscription list wholesale (no merge); omit it to leave it unchanged. An endpoint must subscribe to at least one event, so `[]` and `null` are rejected; delete the endpoint to stop all deliveries. Requires an org management role. Returns `403 manage_endpoints_on_live_api` on the sandbox API; use the live API. operationId: updateWebhookEndpoint security: - bearerAuth: - write:webhooks parameters: - $ref: '#/components/parameters/webhook_external_id' requestBody: content: application/json: schema: type: object properties: url: type: string format: uri events: type: array minItems: 1 items: type: string enum: - investment.created - investment.reinstated - investment.canceled - investment.converted - investment.amount_changed - investment.executed - offering.opened - offering.closing - offering.closed - offering.canceled - investment_session.created - investment_session.started - investment_session.completed - investment_session.expired - investment_session.canceled - investment.changed - syndicate_member.invited - syndicate_member.reinvited - syndicate_member.applied - syndicate_member.approved - syndicate_member.joined responses: '200': description: Updated endpoint (secret omitted) content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const endpoint = await wf.webhookEndpoints.update("whe_8f3ExampleEndpoint00", { events: ["offering.opened", "offering.closed", "investment.executed"], }); console.log(endpoint.attributes?.events); delete: tags: - Webhook Endpoints summary: Remove a webhook endpoint description: | Stops deliveries immediately. The endpoint is retained as removed and cannot be restored. Returns `403 manage_endpoints_on_live_api` on the sandbox API; use the live API. operationId: deleteWebhookEndpoint security: - bearerAuth: - write:webhooks parameters: - $ref: '#/components/parameters/webhook_external_id' responses: '200': description: Removed content: application/json: schema: type: object properties: data: type: object properties: id: type: string type: type: string example: webhook_endpoint removed: type: boolean example: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const result = await wf.webhookEndpoints.remove("whe_8f3ExampleEndpoint00"); console.log(result.removed); // true /webhook_endpoints/{external_id}/rotate_secret: post: tags: - Webhook Endpoints summary: Rotate the signing secret description: | Returns a new signing secret (shown once). The previous secret keeps signing for a 24h overlap window, so deliveries carry a `v1` for both; verify against either. Returns `403 manage_endpoints_on_live_api` on the sandbox API; use the live API. operationId: rotateWebhookEndpointSecret security: - bearerAuth: - write:webhooks parameters: - $ref: '#/components/parameters/webhook_external_id' responses: '200': description: Rotated (includes the new one-time secret) content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const endpoint = await wf.webhookEndpoints.rotateSecret("whe_8f3ExampleEndpoint00"); const newSecret = endpoint.attributes?.secret; // shown once console.log(newSecret); /webhook_endpoints/{external_id}/reenable: post: tags: - Webhook Endpoints summary: Re-enable a disabled endpoint description: | Recovery after auto-disable. Idempotent. Requires an org management role on the live API. Also allowed on the sandbox API for an endpoint sandbox itself disabled (sandbox runs its own health checks on its mirror of a test endpoint), authorized by the caller's realm. operationId: reenableWebhookEndpoint security: - bearerAuth: - write:webhooks parameters: - $ref: '#/components/parameters/webhook_external_id' responses: '200': description: The (now enabled) endpoint content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const endpoint = await wf.webhookEndpoints.reenable("whe_8f3ExampleEndpoint00"); console.log(endpoint.attributes?.enabled); // true /webhook_endpoints/{external_id}/test: post: tags: - Webhook Endpoints summary: Send a test event description: | Sends a real, signed example event (fake values) using the production envelope, signature scheme, and transport, and reports the outcome inline. Does not affect delivery health. Rate-limited to 10 per minute per endpoint. Investor email appears in the example only if your application holds `read:investors:pii`, matching what real deliveries can carry. operationId: testWebhookEndpoint security: - bearerAuth: - write:webhooks parameters: - $ref: '#/components/parameters/webhook_external_id' requestBody: content: application/json: schema: type: object properties: event: type: string description: Event to simulate. Defaults to the endpoint's first subscribed event. enum: - investment.created - investment.reinstated - investment.canceled - investment.converted - investment.amount_changed - investment.executed - offering.opened - offering.closing - offering.closed - offering.canceled - investment_session.created - investment_session.started - investment_session.completed - investment_session.expired - investment_session.canceled - investment.changed - syndicate_member.invited - syndicate_member.reinvited - syndicate_member.applied - syndicate_member.approved - syndicate_member.joined responses: '200': description: Test outcome content: application/json: schema: $ref: '#/components/schemas/WebhookEndpointTestResultEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': description: Endpoint is not subscribed to the event, or the event has no example content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/RateLimitExceeded' x-codeSamples: - lang: JavaScript label: '@wefunder/sdk' source: |- const outcome = await wf.webhookEndpoints.test("whe_8f3ExampleEndpoint00", "investment.executed"); if (outcome.delivered) { console.log(`endpoint answered ${outcome.response_code} in ${outcome.duration_ms}ms`); } else { console.log(`no response: ${outcome.error}`); // timeout | blocked_url | tls_error | connection_failed } /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:explore: See the deals the authorizing user can see on wefunder.com/explore (public plus accredited-only / invited rounds they qualify for); user tokens only read:mcp: Umbrella granted to AI agents connecting through the Wefunder MCP server; implies the read scopes the MCP tools need (read:explore, read:profile, read:syndicates, read:companies, read:offerings). Not self-serve. 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 read:installations: View where your app is installed write:installations: Install and uninstall your app on companies and syndicates you can edit 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: wefunder_version: name: Wefunder-Version in: header required: false description: | Dated API version (`YYYY-MM-DD`). Omit to use your application's pinned version. Unknown values return `400 invalid_request`. Every response echoes the resolved version in `Wefunder-Version` and the newest in `Wefunder-Version-Latest`. schema: type: string pattern: ^\d{4}-\d{2}-\d{2}$ example: '2026-10-01' 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's id (`mem_...`), as returned by the members list (`data[].id`). Integer ids are internal and not accepted. schema: type: string example: mem_aB3xQ9k2vF8mNp1zT5wY7Qc4 fundraise_id: name: fundraise_id in: path required: true description: The deal's id (`ofr_...`), as returned by the deals list. Integer ids are internal and not accepted. schema: type: string example: ofr_yw3OhusvpfJP4Pk3wRYV1F2R 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_external_id: name: external_id in: path required: true description: The webhook endpoint's id (whe_...). schema: type: string example: whe_8f3ExampleEndpoint00 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: Installation: type: object properties: id: type: string description: Installation external id (`inst_…`). type: type: string example: installation attributes: type: object properties: target: type: object properties: type: type: string enum: - company - syndicate id: type: string description: '`co_…` or `syn_…`' name: type: string tier: type: string description: founder / admin / editor for a company; full_access / operator for a syndicate. scopes: type: array items: type: string status: type: string enum: - active - revoked installed_at: type: string format: date-time installed_by: type: string nullable: true revoked_at: type: string format: date-time nullable: true InstallationEnvelope: type: object properties: data: $ref: '#/components/schemas/Installation' meta: type: object InstallationListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/Installation' meta: type: object properties: count: type: integer InstallationTokenEnvelope: type: object properties: data: $ref: '#/components/schemas/Installation' token: type: object description: Shown once. Owned by the installed-on company or syndicate, scoped to the install, no expiry; revoking the install revokes it. properties: access_token: type: string example: at_live_… token_type: type: string example: Bearer scope: type: string installation: type: string description: '`inst_…`' created_at: type: integer meta: type: object EligibleTargetListEnvelope: type: object properties: data: type: array items: type: object properties: type: type: string enum: - company - syndicate id: type: string name: type: string tier: type: string description: The tier an install would be granted at. installed: type: boolean installation: nullable: true allOf: - $ref: '#/components/schemas/Installation' meta: type: object WebhookEndpoint: type: object properties: id: type: string example: whe_8f3ExampleEndpoint00 type: type: string example: webhook_endpoint attributes: type: object properties: url: type: string format: uri mode: type: string enum: - live - test events: type: array items: type: string enabled: type: boolean secret: type: string description: Signing secret. Present only in create and rotate_secret responses. failing_since: type: string format: date-time nullable: true last_delivery_at: type: string format: date-time nullable: true last_delivery_status: type: string nullable: true description: | Outcome of the most recent delivery: the HTTP status code as a string (for example `"200"` or `"503"`), or, when no response came back, one of `timeout`, `blocked_url`, `tls_error`, `connection_failed`. example: '200' created_at: type: string format: date-time WebhookEndpointEnvelope: type: object properties: data: $ref: '#/components/schemas/WebhookEndpoint' meta: type: object properties: request_id: type: string WebhookEndpointListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/WebhookEndpoint' meta: type: object properties: count: type: integer quota: type: integer description: Maximum endpoints allowed for this application. request_id: type: string WebhookEndpointTestResultEnvelope: type: object properties: data: type: object properties: type: type: string example: webhook_test delivered: type: boolean response_code: type: integer nullable: true error: type: string nullable: true enum: - timeout - blocked_url - tls_error - connection_failed - null description: | Why no HTTP response came back. `null` when the endpoint responded, even with a non-2xx status (see `response_code`). `blocked_url` means the URL failed the HTTPS/public-address check at send time. duration_ms: type: integer event: type: string payload: type: object description: The exact signed envelope that was POSTed. meta: type: object properties: request_id: type: string InvestmentChangeListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/InvestmentDeltaRecord' meta: type: object properties: mode: type: string enum: - bootstrap - delta has_more: type: boolean next_cursor: type: string description: Always present. Pass it back as `cursor` on the next call, even when `has_more` is false. page_count: type: integer company_id: type: string InvestmentDeltaRecord: type: object description: | The record of one investment, or a tombstone (`visible: false`, only `id`). `visible` is founder visibility: a user's own canceled investment is a full record with `visible: false` and `status: canceled`. Investor PII keys are omitted without `read:investors:pii`, except on the user's own records. required: - id - visible - observed_at properties: id: type: string description: Investment external id (`inv_…`), stable for the life of the investment. visible: type: boolean status: type: string enum: - reserved - active - executed - canceled - converted description: Company/offering audiences receive canceled and converted investments as tombstones; the values appear for audiences that can see them (e.g. the investor's own). converted_to: type: string nullable: true converted_from: type: string nullable: true offering: type: string description: Offering external id (`ofr_…`). company: type: string description: Company external id (`co_…`). investment_type: type: string offering_type: type: string group: type: string description: The founder dashboard's investment group label (e.g. CONFIRMED, IS READY). applied_at: type: string format: date-time amounts: type: object properties: committed_cents: type: integer investment_size_cents: type: integer in_escrow_cents: type: integer raised_cents: type: integer description: What this investment contributes to the offering's public raised figure (the deal page, company card and directory header). Equals `investment_size_cents` once the investment is soft-confirmed; 0 until then (see `needs_whitelisting`). Summing `raised_cents` over an offering's records reproduces the site's number; summing `committed_cents` does not. currency: type: string example: usd shares: type: string nullable: true description: Shares this investment buys at its committed amount, as a decimal string (`"295"`, `"1234.5"`). For an investment through the SPV this is the company shares allotted to it through the vehicle. `null` when the round has no share concept (SAFE, note, reservation) or no shares are computed for the investment. Computed per investment; not the investor's post-funding position. example: '295' average_share_price: type: string nullable: true description: Weighted average price per share for this investment (total share price / `shares`), as a decimal string. With tiered or early-bird pricing this need not equal any single price on the round. `null` whenever `shares` is `null`. example: '3.38' needs_whitelisting: type: boolean external_username: type: string nullable: true description: PII scope. message: type: string nullable: true description: PII scope. investor: type: object properties: id: type: string description: Investor external id (`usr_…`). deactivated: type: boolean description: | True once the investor's Wefunder account has been deactivated. The identity fields are then redacted (`name`/`legal_name` = `[deleted user]`, the rest null) and the record is republished with `reason: investor_deactivated`; overwrite your copy. The investment itself persists. via_entity: type: boolean name: type: string description: PII scope. legal_name: type: string description: PII scope. email: type: string nullable: true description: PII scope. address: type: object description: PII scope. bio: type: string nullable: true description: PII scope. blockers: type: array items: type: object properties: key: type: string description: type: string nullable: true contracts: type: array items: type: object properties: name: type: string nullable: true override_amount_cents: type: integer nullable: true early_bird: type: boolean observed_at: type: string format: date-time description: When this payload was observed by the publisher. Not a version. cursor: type: string nullable: true description: Ledger position of the change that listed this record (delta pages only). reason: type: string nullable: true enum: - changed - reprojected - investor_deactivated description: | Why the ledger row exists. `investor_deactivated` marks the first republish after the investor's account was deactivated (identity fields redacted). Delta pages only. 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: count: type: integer description: Number of investors on this page total_count: type: integer description: Total investors in the deal offset: type: integer per_page: type: integer has_more: type: boolean 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 whole dollars, as a string example: '8000' 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 filters: type: object description: The filters applied to this response (validated values), empty when none. example: security: safe testing_the_waters: false 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: $ref: '#/components/schemas/PaginationMeta' 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' IntentPreviewEnvelope: type: object properties: data: type: object properties: type: type: string enum: - intent_preview attributes: type: object properties: action: type: string resource_type: type: string resource_id: type: string description: The resource's external id (`co_...`, `syn_...`). params: type: object impact_summary: type: string description: What the review page would show the approver. scope: type: string description: The scope `POST /intents` requires for this action. CommentProposalEnvelope: type: object description: What propose_comment returns — a dry run of comments.create plus the context an agent needs before minting. properties: proposal: type: object description: The arguments, as normalized, to pass to write_comment_intent once the user confirms. properties: company_id: type: string target_type: type: string enum: - company - comment target: type: string body: type: string disclosure_key: type: string nullable: true allowed: type: boolean refusal: type: string nullable: true description: The handler's reason when `allowed` is false. draft: type: object nullable: true description: The dry run (null when `allowed` is false). Same shape as IntentPreviewEnvelope's attributes. properties: action: type: string resource_type: type: string resource_id: type: string params: type: object impact_summary: type: string scope: type: string related_questions: type: array items: type: object properties: id: type: string question: type: string nullable: true asked_at: type: string nullable: true answered_by_team: type: boolean answers: type: array items: type: string nullable: true url: type: string nullable: true drafting_guidelines: type: string next_step: type: string 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 description: The intent's id (`int_...`). Poll it at `GET /intents/{intent_id}`. example: int_aB3xQ9k2vF8mNp1zT5wY7Qc4 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/InvestmentDeltaRecord' meta: type: object properties: mode: type: string enum: - bootstrap - delta description: '`bootstrap` for list pages, `delta` for sync pages (cursor or updated_since).' has_more: type: boolean next_cursor: type: string description: Always present. Pass it back as `cursor` on the next call, even when `has_more` is false. page_count: type: integer published_through: type: string format: date-time nullable: true description: When the newest change this page can reflect was published. Records changed after this arrive on the next sync. InvestmentEnvelope: type: object properties: data: $ref: '#/components/schemas/InvestmentDeltaRecord' meta: type: object properties: source: type: string enum: - current published_observed_at: type: string format: date-time nullable: true description: '`observed_at` of the published record, or null if never published.' published_matches: type: boolean nullable: true description: Whether the published record equals this current one; null if never published. OfferingStatsEnvelope: type: object properties: data: type: object properties: offering: type: string company: type: string currency: type: string nullable: true by_status: type: object additionalProperties: type: object properties: count: type: integer committed_cents: type: integer raised_cents: type: integer total: type: object properties: count: type: integer committed_cents: type: integer raised_cents: type: integer description: The offering's public raised figure rebuilt from the records' `amounts.raised_cents`. The campaign ticker may add sibling Reg D rounds under its roll-in policy. meta: type: object properties: source: type: string enum: - published published_through: type: string format: date-time nullable: true 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/SyndicateDeal' 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/Syndicate' 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' FollowedCompany: type: object description: One company on the authenticated user's watchlist (GET /users/me/follows). properties: id: type: string description: The company's id (`co_...`) for `GET /companies/{id}`. example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY type: type: string enum: - company attributes: type: object properties: name: type: string nullable: true tagline: type: string nullable: true url: type: string nullable: true logo_url: type: string nullable: true raising: type: boolean description: True when the company has a round accepting investments right now. profile_available: type: boolean description: Whether `GET /companies/{id}` will serve this company to this user. followed_at: type: string format: date-time nullable: true FollowedCompanyListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/FollowedCompany' meta: $ref: '#/components/schemas/PaginationMeta' FollowStateEnvelope: type: object properties: data: type: object properties: id: type: string nullable: true description: The company's id (`co_...`). type: type: string enum: - follow_state attributes: type: object properties: followed: type: boolean description: Whether the user follows the company after this call. changed: type: boolean description: Whether this call changed anything (false when already in the requested state). MyCompany: type: object description: One of the authenticated user's own companies (GET /users/me/companies). properties: id: type: string description: The company's id (`co_...`) — what the founder-scoped endpoints take. example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY type: type: string enum: - company attributes: type: object properties: name: type: string example: Acme Robotics tagline: type: string nullable: true url: type: string nullable: true description: The company's Wefunder page. example: https://wefunder.com/acme roles: type: array description: The user's roles on this company. items: type: string example: - founder raising: type: boolean description: True when a round is currently accepting investments or reservations. MyCompanyListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/MyCompany' meta: type: object properties: count: type: integer 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: oneOf: - $ref: '#/components/schemas/Security' nullable: true description: | What the offering issues, as a discriminated union on `type` (see `Security`). Null for a Testing-the-Waters round, which has no final terms yet — see `intended_security` for what it plans to issue. intended_security: type: object nullable: true description: | Testing-the-Waters rounds only: the kind of security the company says it intends to issue when the round opens, with no terms (none are final). Null for a live round, and for a TTW round whose terms are still to be decided. properties: type: type: string enum: - safe - equity - convertible_note - revenue_share - debt - fund label: type: string example: SAFE industries: type: array description: | The company's curated industry tags (the ones the site's explore page filters by), as `{ type, label }`. Empty when none are set. Filterable via `industry`. items: $ref: '#/components/schemas/TagRef' business_models: type: array description: The company's curated business-model tags, as `{ type, label }`. Filterable via `business_model`. items: $ref: '#/components/schemas/TagRef' status: type: string enum: - upcoming - open - closed - canceled description: | Where the round is in its life. `open` accepts investments (or reservations, for a Testing-the-Waters round); `closed` finished raising; `upcoming` is not yet accepting (insiders only); `canceled` was aborted. How close an open round is to closing is `closes_at`; whether it is over target is `amount_raised` vs `funding_target`. 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 **by this offering** in USD, as a decimal string (hellbanned investors excluded). Not the company's lifetime total, and not the combined figure the company's Wefunder page shows when a Reg D round runs alongside — see `warnings`. example: '642300' investor_count: type: integer nullable: true description: Distinct investor count for this offering (hellbanned investors excluded). example: 1203 started_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' closes_at: type: string format: date-time nullable: true description: When an open round is scheduled to stop accepting investments. Null when no date is set or the round is not open. example: '2025-04-30T03:59:59Z' closed_at: type: string format: date-time nullable: true example: '2025-03-01T12:00:00Z' publicly_visible: type: boolean description: | **Logged-in view only** (`read:explore`). `false` when a logged-out visitor could not see this offering — it is in the response only because of who the authorizing user is (accredited, invited, Vault/syndicate member). Absent in the public view. example: true invested: type: boolean description: | **Logged-in view only** (`read:explore`). Whether the authorizing user has an active investment in this offering. Absent in the public view. example: false followed: type: boolean description: | **Logged-in view only** (`read:explore`). Whether the authorizing user follows this company on wefunder.com (their watchlist). Absent in the public view. example: false company: type: string nullable: true description: The parent company's id (`co_...`). Resolve it with `GET /companies/{id}`. example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY warnings: type: array description: | Context for reading this offering's numbers correctly. Empty for most offerings. `concurrent_rounds`: the company has other open rounds (e.g. a Reg D round alongside this one), so the company's Wefunder page shows a combined total larger than `amount_raised`. `prior_rounds`: the company completed earlier rounds on Wefunder, so `amount_raised` is not its lifetime total. Not part of the `etag`. items: type: object properties: code: type: string enum: - concurrent_rounds - prior_rounds example: prior_rounds message: type: string example: This company completed 2 earlier rounds on Wefunder. amount_raised and investor_count cover this offering only, not the company's lifetime total. etag: type: string description: | Content digest of this offering's public payload (`attributes`), for client-side change detection when polling. `warnings` are not included. example: a1b2c3d4e5f6 CompanySearchResult: type: object description: One company from the site search bar (GET /companies/search). properties: id: type: string description: The company's id (`co_...`) for `GET /companies/{id}`. example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY type: type: string enum: - company attributes: type: object properties: name: type: string nullable: true example: Acme Robotics tagline: type: string nullable: true url: type: string nullable: true description: The company's Wefunder page. example: https://wefunder.com/acme logo_url: type: string nullable: true raising: type: boolean description: | True when the company is raising now (the site's "Raising Now" badge). False means the site shows its "Funded" badge, which it also shows for companies with no live round (including ones whose last round was aborted); it is not a statement that a raise closed successfully. profile_available: type: boolean description: | Whether `GET /companies/{id}` will serve this company to this viewer. Since the company page serves every publicly listed profile, this is false only for an index hit whose live row no longer clears the site's bar (or an accredited-only company seen without accreditation). Link to `url` when false. CompanySearchResultListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/CompanySearchResult' meta: type: object properties: query: type: string count: type: integer CompanyDisclosuresEnvelope: type: object properties: data: $ref: '#/components/schemas/CompanyDisclosures' CompanyDisclosures: type: object description: The company's public Form C disclosures (the site's Details tab), one section per key. A section is null when the company hides it on the site. properties: id: type: string description: The company's id (`co_...`). type: type: string enum: - company_disclosures attributes: type: object properties: offering_id: type: string description: The round whose Form C this is (`ofr_...`). filing: type: object properties: exemption: $ref: '#/components/schemas/Exemption' sec_filing_url: type: string nullable: true description: The filing on sec.gov, when filed. filed: type: boolean statement_date: type: string format: date nullable: true description: The date the financial statements are as of. fiscal_year_end: type: string nullable: true description: '`--MM-DD` (ISO 8601 recurring date), e.g. `--12-31`.' business: type: object properties: legal_name: type: string nullable: true legal_form: type: string nullable: true jurisdiction: type: string nullable: true incorporated_on: type: string format: date nullable: true employees: type: integer nullable: true financial_statements: type: object description: One column per fiscal year on file; a year with no figures is omitted. properties: most_recent: $ref: '#/components/schemas/FiscalYearFinancials' prior: $ref: '#/components/schemas/FiscalYearFinancials' prior_prior: $ref: '#/components/schemas/FiscalYearFinancials' ratios: type: object nullable: true description: The page's at-a-glance ratios for the most recent fiscal year, percentages as decimal strings; null where the page shows N/A, or the whole block null when hidden for this company. properties: net_margin_percent: type: string nullable: true description: Net income / revenue × 100. gross_margin_percent: type: string nullable: true description: (Revenue − cost of goods sold) / revenue × 100. return_on_assets_percent: type: string nullable: true description: Net income / total assets × 100. debt_to_assets_percent: type: string nullable: true description: (Short-term + long-term debt) / total assets × 100. cash_to_assets_percent: type: string nullable: true description: Cash / total assets × 100. revenue_per_employee: type: string nullable: true description: USD, decimal string. current_position: type: object description: The founder's own disclosure of where the company stands today, each field when supplied. properties: cash_on_hand: type: string nullable: true description: USD, decimal string. cash_on_hand_as_of: type: string format: date nullable: true average_monthly_revenue: type: string nullable: true description: USD, decimal string. average_monthly_cost_of_goods: type: string nullable: true description: USD, decimal string. average_monthly_expenses: type: string nullable: true description: USD, decimal string. average_monthly_burn: type: string nullable: true description: USD, decimal string. financial_condition: type: string nullable: true description: The issuer's financial-condition narrative, plain text. financial_statement_documents: type: array items: $ref: '#/components/schemas/DisclosureDocument' investment_documents: type: array description: The contracts an investor signs (the page's "Investment Terms"). items: $ref: '#/components/schemas/DisclosureDocument' risks: type: array description: The issuer's risk factors, in the page's order, plain text. items: type: string use_of_funds: type: array nullable: true description: How the company says it will use the money at each amount raised. items: type: object properties: if_raised: type: string nullable: true description: USD, decimal string. plan: type: string nullable: true directors: type: array nullable: true items: $ref: '#/components/schemas/DisclosurePerson' officers: type: array nullable: true items: $ref: '#/components/schemas/DisclosurePerson' voting_power: type: array nullable: true description: Holders of 20% or more of voting power, as disclosed. items: type: object properties: name: type: string nullable: true holding: type: string nullable: true description: Number and class of securities held, as disclosed. voting_power_percent: type: string nullable: true capital_structure: type: array nullable: true items: type: object properties: class_of_security: type: string nullable: true authorized: type: string nullable: true outstanding: type: string nullable: true voting_rights: type: string nullable: true other_rights: type: string nullable: true prior_offerings: type: array nullable: true description: Exempt offerings in the prior three years, as disclosed. items: type: object properties: date: type: string format: date nullable: true exemption: type: string nullable: true security_type: type: string nullable: true amount_sold: type: string nullable: true use_of_proceeds: type: string nullable: true offering_id: type: string nullable: true description: The Wefunder offering (`ofr_...`) when the prior offering ran here. outstanding_notes: type: array nullable: true items: type: object properties: amount: type: string nullable: true outstanding_principal: type: string nullable: true valuation_cap: type: string nullable: true uncapped: type: boolean interest_rate_percent: type: string nullable: true discount_percent: type: string nullable: true maturity_date: type: string format: date nullable: true description: type: string nullable: true outstanding_debts: type: object nullable: true properties: note: type: string nullable: true description: A company-specific note the site shows instead of the table, when set. items: type: array items: type: object properties: original_amount: type: string nullable: true outstanding_principal: type: string nullable: true current_with_payments: type: boolean nullable: true maturity_date: type: string format: date nullable: true description: type: string nullable: true related_parties: type: object nullable: true properties: description: type: string nullable: true parties: type: array items: type: object properties: name: type: string nullable: true relationship: type: string nullable: true amount: type: string nullable: true date: type: string format: date nullable: true outstanding_principal: type: string nullable: true description: type: string nullable: true FiscalYearFinancials: type: object description: One fiscal year's figures from the Form C, USD decimal strings (null where not reported). properties: total_assets: type: string nullable: true cash_and_equivalents: type: string nullable: true accounts_receivable: type: string nullable: true short_term_debt: type: string nullable: true long_term_debt: type: string nullable: true revenue: type: string nullable: true cost_of_goods_sold: type: string nullable: true taxes_paid: type: string nullable: true net_income: type: string nullable: true DisclosureDocument: type: object properties: name: type: string nullable: true url: type: string DisclosurePerson: type: object properties: name: type: string nullable: true titles: type: string nullable: true since: type: integer nullable: true description: Year joined. director: type: boolean officer: type: boolean CompanyUpdate: type: object description: One post from a company's Posts tab. Plain text, never HTML. properties: id: type: string nullable: true description: The post's public id (its share token); pass as `update_id` for the full text. type: type: string enum: - company_update attributes: type: object properties: kind: type: string description: '`update`, `note`, `spotlight`, `bounty`, ...' title: type: string nullable: true excerpt: type: string nullable: true description: The first ~280 characters, plain text. content: type: string nullable: true description: Full plain text. Present on the single-post endpoint; null in lists. published_at: type: string format: date-time nullable: true pinned: type: boolean visibility: type: string nullable: true enum: - public - investors - community - founders - private - null description: Who the site shows the post to. You only ever receive posts you may see. author: type: object nullable: true properties: name: type: string nullable: true comments_count: type: integer likes_count: type: integer url: type: string nullable: true description: The post's page on wefunder.com. CompanyUpdateListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/CompanyUpdate' meta: type: object properties: company: type: string description: The company's id (`co_...`). has_more: type: boolean page_count: type: integer next_cursor: type: integer nullable: true CompanyUpdateEnvelope: type: object properties: data: $ref: '#/components/schemas/CompanyUpdate' CompanyPitchEnvelope: type: object properties: data: $ref: '#/components/schemas/CompanyPitch' CompanyPitch: type: object description: The company page's pitch (the Overview tab's story) as ordered blocks, plus the round's perk tiers. properties: id: type: string description: The company's id (`co_...`). type: type: string enum: - company_pitch attributes: type: object properties: offering_id: type: string nullable: true description: The round the page shows this viewer (`ofr_...`), whose perks these are; null when the viewer may see none. title: type: string description: The heading the page puts over the story (the founder's own, or the default). url: type: string nullable: true description: The company's page on wefunder.com. authored_by: type: string enum: - company - wefunder description: '`company`: the founder wrote the story. `wefunder`: a Wefunder-prepared deal memo the company did not participate in.' disclaimer: type: string nullable: true description: The page's notice on a Wefunder-prepared deal memo; null when the company authored the story. story: type: object description: The story in document order, as the page renders it. Empty `blocks` when the company has no story. properties: blocks: type: array items: $ref: '#/components/schemas/PitchBlock' image_count: type: integer video_count: type: integer inline_images_omitted: type: integer description: Images the page renders from an inline `data:` URL, which have no fetchable URL and are left out of `blocks`. Almost always 0. character_count: type: integer description: Characters of text across paragraph, heading, list, and footnote blocks. perks: type: object nullable: true description: The perk tiers the page shows; null when the viewer may see no round. properties: offering_id: type: string nullable: true description: The round these perks belong to (`ofr_...`) when it is the round the page shows. Null when the sidebar reads a round the viewer cannot resolve yet (a Testing-the-Waters page shows the perks of the round it converts into, which is still being set up). currency: type: string description: ISO 4217 code the tier amounts are denominated in, e.g. `USD` or `EUR`. described_in_pitch: type: boolean description: Heuristic. True when the tiers are a placeholder ("See investor overview page", or one identical short sentence on every tier) and the real perks are in the story, usually as images. tiers: type: array items: $ref: '#/components/schemas/PerkTier' PitchBlock: type: object description: One block of the story. `type` says which of the other keys are present. properties: type: type: string enum: - heading - paragraph - list - image - video - footnote level: type: integer description: Heading level 1–6 (`heading` only). text: type: string description: Plain text (`heading`, `paragraph`, and `footnote`). A footnote reference in running text appears as `[n]`. number: type: integer description: The footnote's number, matching its `[n]` reference (`footnote` only). ordered: type: boolean description: Numbered list (`list` only). start: type: integer description: First number of an ordered list that resumes after an image or nested list split it (`list` only, when above 1). items: type: array items: type: string description: Plain-text list items (`list` only). links: type: array description: The hyperlinks in this block's text, in order, as the page links them (`paragraph`, `heading`, `list`; absent when there are none). Anchor text stays inline in `text` / `items`. items: type: object properties: text: type: string nullable: true description: The anchor text. url: type: string description: Absolute URL; site-relative hrefs are resolved against wefunder.com. url: type: string description: Absolute URL of the image or video (`image` and `video`). link: type: string description: Where the page links this image or video to, when it is wrapped in a hyperlink (`image` and `video`; absent otherwise). alt: type: string nullable: true description: The image's alt text; almost always null on Wefunder pitches (`image` only). filename: type: string nullable: true description: The image file's name, often the only hint at its content, e.g. `Tier_3_Final.png` (`image` only). provider: type: string description: '`youtube`, `vimeo`, `wistia`, `loom`, `upload` (hosted by Wefunder), or `other` (`video` only).' PerkTier: type: object properties: qualifying_amount: type: string description: Investment that unlocks the tier, as a decimal string in `perks.currency`. description: type: string nullable: true description: Plain text, the founder's own words. CompanyQuestionPerson: type: object properties: id: type: string nullable: true description: The person's Wefunder id (`usr_...`). example: usr_7Hk2mPq9rT4vX1yB3nC5dF8g name: type: string nullable: true role: type: string nullable: true description: 'The label the tab shows: `founder`, `employee`, `wefunder team`, or the asker''s own disclosure (e.g. `investor`).' team: type: boolean description: True when the person is on the company's team (a founder or employee). CompanyQuestion: type: object description: One question from a company's Ask tab with its answers. Plain text. properties: id: type: string description: The question's id (`cmt_...`). example: cmt_m2JkUx7E3aBFscGMKDdJ9saR type: type: string enum: - company_question attributes: type: object properties: question: type: string nullable: true asked_at: type: string format: date-time nullable: true asked_by: $ref: '#/components/schemas/CompanyQuestionPerson' likes_count: type: integer highlighted: type: boolean description: The team highlighted this question on the tab. answered_by_team: type: boolean match: type: string enum: - question - answer nullable: true description: In a search (`q`), whether the question's own text or one of its answers matched. Null otherwise. answers: type: array items: type: object properties: id: type: string description: The answer's id (`cmt_...`). answer: type: string nullable: true answered_at: type: string format: date-time nullable: true answered_by: $ref: '#/components/schemas/CompanyQuestionPerson' likes_count: type: integer url: type: string nullable: true description: The company's Ask tab on wefunder.com. CompanyQuestionListEnvelope: type: object properties: data: type: array items: $ref: '#/components/schemas/CompanyQuestion' meta: type: object properties: company: type: string sort: type: string description: The sort applied; `search` when `q` was given (results are ranked by match). q: type: string nullable: true unanswered_by_team: type: boolean past_raises: type: boolean questions_since: type: string format: date nullable: true description: The current raise's opening date when `past_raises` is false; null otherwise. total: type: integer description: Questions matching, across all pages. has_more: type: boolean page_count: type: integer next_cursor: type: integer nullable: true CompanyEnvelope: type: object properties: data: $ref: '#/components/schemas/Company' Company: type: object description: The company page as structured data. Monetary fields are USD decimal strings. properties: id: type: string description: The company's id (`co_...`). example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY type: type: string example: company attributes: type: object properties: name: type: string tagline: type: string nullable: true url: type: string nullable: true description: The company's Wefunder page. logo_url: type: string nullable: true card_image_url: type: string nullable: true location: type: object properties: city: type: string nullable: true state: type: string nullable: true country: type: string nullable: true raising: type: boolean description: True when the round the page shows this viewer is live (accepting investments or reservations). False for a funded company, or one with no round the viewer may see. current_raise: nullable: true allOf: - $ref: '#/components/schemas/CurrentRaise' description: The live round the page shows, or null when `raising` is false. A funded company still has its history in `wefunder_rounds` and the page's ticker in `totals`. past_rounds: type: array description: The prior rounds the company page's ticker folds in (its own accounting; see `totals.profile_*`). Not the company's full Wefunder history — that is `wefunder_rounds`. items: $ref: '#/components/schemas/PastRound' wefunder_rounds: type: array description: Every round this company has run on Wefunder that the viewer may see, live and closed, newest first, each with its own metric. Independent of what the page's ticker chooses to include. items: $ref: '#/components/schemas/WefunderRound' totals: $ref: '#/components/schemas/CompanyTotals' CurrentRaise: type: object description: The round the page shows this viewer, with its linked legs combined. properties: offering_id: type: string description: The displayed offering (`ofr_...`); fetch it with `/offerings/{id}`. offering_ids: type: array description: Every live leg combined into `amount_raised` that the viewer may see (e.g. a Reg CF round and its Reg D round). items: type: string testing_the_waters: type: boolean description: True when the current raise collects non-binding reservations, not investments. amount_raised: type: string nullable: true description: Raised across the combined legs, on Wefunder, USD decimal string. funding_target: type: string nullable: true investor_count: type: integer nullable: true oversubscribed: type: boolean closes_at: type: string format: date-time nullable: true PastRound: type: object properties: offering_id: type: string nullable: true description: The Wefunder offering (`ofr_...`) when `source` is `wefunder`; null for a reported round. source: type: string enum: - wefunder - reported description: '`wefunder`: observed on this platform. `reported`: disclosed by the founder as raised off-platform (verified per the page''s rules, but not observed here).' exemption: $ref: '#/components/schemas/Exemption' amount_raised: type: string nullable: true investor_count: type: integer nullable: true opened_at: type: string format: date-time nullable: true closed_at: type: string format: date-time nullable: true WefunderRound: type: object properties: offering_id: type: string description: The round's offering id (`ofr_...`). current: type: boolean description: True for the displayed round and any other live round. status: type: string enum: - open - closed exemption: oneOf: - $ref: '#/components/schemas/Exemption' nullable: true security: oneOf: - $ref: '#/components/schemas/Security' nullable: true amount_raised: type: string nullable: true description: This round's own soft-confirmed amount, USD decimal string. investor_count: type: integer nullable: true opened_at: type: string format: date-time nullable: true closed_at: type: string format: date-time nullable: true CompanyTotals: type: object properties: raised_on_wefunder_all_time: type: string nullable: true description: Sum of `wefunder_rounds[].amount_raised` — everything this company has raised on Wefunder that the viewer may see. The lifetime figure. profile_total_raised: type: string nullable: true description: The number on the company page's ticker bar — the current raise plus the past rounds the page folds in (which may include founder-reported off-platform rounds and exclude older Wefunder rounds). profile_reported_off_platform: type: string nullable: true description: The founder-reported off-platform portion of `profile_total_raised`. profile_includes_past_rounds: type: boolean nullable: true description: Whether the page's ticker currently folds past rounds into its total. Null when the viewer may see no round (no ticker to read). 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 - other description: | `other` appears only on a company's past rounds, for a reported round under an exemption outside this list (e.g. Section 4(a)(2)). `reg_cf` — Regulation Crowdfunding: open to all investors, with per-investor annual limits set by the SEC; amounts and investor counts are public. `reg_d` — Regulation D private placement: `506c` is open to verified accredited investors only and may be advertised; `506b` is by invitation and never publicly listed. `reg_a`, `reg_s` and `ecsp` exist in the data but are not filterable on `/explore`. 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 TagRef: type: object description: A curated company tag — a machine value plus the label the site shows. required: - type - label properties: type: type: string example: robotics label: type: string example: Robotics SecuritySummary: type: object description: | The kind of security without its terms — the `type` / `label` pair shared by every `Security` variant. Used where the terms live elsewhere (portfolio positions) or are not final (a Testing-the-Waters round's `intended_security`). required: - type - label properties: type: type: string enum: - safe - equity - convertible_note - revenue_share - debt - fund - other example: safe label: type: string example: SAFE Security: description: | What an offering issues. A discriminated union on `type`: every variant carries the same `type` / `label` pair and a `terms` object whose fields are fixed per type. Money and rates are USD decimal strings. Internal instrument codes never appear here. oneOf: - $ref: '#/components/schemas/SafeSecurity' - $ref: '#/components/schemas/EquitySecurity' - $ref: '#/components/schemas/ConvertibleNoteSecurity' - $ref: '#/components/schemas/RevenueShareSecurity' - $ref: '#/components/schemas/DebtSecurity' - $ref: '#/components/schemas/FundSecurity' - $ref: '#/components/schemas/OtherSecurity' discriminator: propertyName: type mapping: safe: '#/components/schemas/SafeSecurity' equity: '#/components/schemas/EquitySecurity' convertible_note: '#/components/schemas/ConvertibleNoteSecurity' revenue_share: '#/components/schemas/RevenueShareSecurity' debt: '#/components/schemas/DebtSecurity' fund: '#/components/schemas/FundSecurity' other: '#/components/schemas/OtherSecurity' SafeSecurity: type: object properties: type: type: string enum: - safe label: type: string example: SAFE terms: type: object properties: valuation_cap: type: string nullable: true description: Post-money valuation cap in USD, decimal string; null for an uncapped SAFE (never "0"). example: '10000000' uncapped: type: boolean description: True when the SAFE has no valuation cap, as the deal page labels it. Always the inverse of `valuation_cap` being present. discount_percent: type: string nullable: true description: Discount to the next priced round, as a percentage; null when none. example: '20' most_favored_nation: type: boolean description: Whether the SAFE carries an MFN clause. pro_rata: type: boolean description: Whether investors receive pro-rata rights in the next round. EquitySecurity: type: object properties: type: type: string enum: - equity label: type: string example: Equity terms: type: object properties: share_price: type: string nullable: true description: Price per share in USD, decimal string. example: '2.5' pre_money_valuation: type: string nullable: true description: Pre-money valuation in USD, decimal string. example: '15000000' share_class: type: string nullable: true enum: - preferred - common - null example: preferred ConvertibleNoteSecurity: type: object properties: type: type: string enum: - convertible_note label: type: string example: Convertible Note terms: type: object properties: valuation_cap: type: string nullable: true description: Valuation cap in USD, decimal string; null for an uncapped note (never "0"). example: '5000000' uncapped: type: boolean description: True when the note has no valuation cap, as the deal page labels it. discount_percent: type: string nullable: true example: '20' interest_rate_percent: type: string nullable: true description: Annual interest, as a percentage. example: '6' maturity_months: type: integer nullable: true example: 24 most_favored_nation: type: boolean RevenueShareSecurity: type: object properties: type: type: string enum: - revenue_share label: type: string example: Revenue Share terms: type: object properties: revenue_share_percent: type: string nullable: true description: Share of revenue paid to investors each period, as a percentage. example: '10' repayment_cap_multiple: type: string nullable: true description: Payments stop once investors have received this multiple of their investment. example: '1.5' payment_period: type: string nullable: true example: quarterly revenue_basis: type: string nullable: true enum: - gross - net - null secured: type: boolean guarantor: type: string nullable: true DebtSecurity: type: object properties: type: type: string enum: - debt label: type: string example: Debt terms: type: object properties: interest_rate_percent: type: string nullable: true example: '8' maturity_months: type: integer nullable: true example: 36 first_payment_date: type: string format: date nullable: true FundSecurity: type: object description: An interest in a fund vehicle; the fund's own terms are on its profile, not per-offering. properties: type: type: string enum: - fund label: type: string example: Fund terms: type: object properties: {} OtherSecurity: type: object description: A security Wefunder does not model in detail; the founder's own name and description for it. properties: type: type: string enum: - other label: type: string example: Other terms: type: object properties: name: type: string nullable: true example: Preferred Membership Units description: type: string nullable: true more_info_url: type: string nullable: true User: type: object properties: id: type: string description: Partner-facing user id (`usr_…`). example: usr_8Kd0aB3xQ9k2vF8mNp1zT5wY 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' 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 deprecated: true description: Internal integer id. Deprecated — use `company` (`co_...`) instead. example: 101 company: type: string nullable: true description: The company's id (`co_...`). example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY 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: page_count: type: integer description: Number of records on this page. example: 25 has_more: type: boolean example: true next_cursor: oneOf: - type: integer - type: string nullable: true description: | Opaque cursor — pass back as `cursor` for the next page. An integer id for id-paginated lists, an ISO 8601 timestamp for timestamp-paginated ones (`/activity`). Absent or null on the last page. 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 syndicate_type: type: string nullable: true description: How the syndicate invests (e.g. `annual_subscription`, `traditional_fund`); null for deal-by-deal syndicates. auto_accept_accredited_members: type: boolean description: Whether accredited applicants are approved automatically. example: false fund_required: type: boolean description: Whether members must invest through the syndicate's fund. example: false annual_subscription: type: object description: Present only when `syndicate_type` is `annual_subscription`. properties: fund_investment_amount: type: string nullable: true deals_per_year: type: integer nullable: true amount_per_deal: type: string nullable: true traditional_fund: type: object description: Present only when `syndicate_type` is `traditional_fund`. properties: require_minimum_investment: type: boolean minimum_investment_amount: type: string nullable: true member_count: type: integer example: 47 deal_count: type: integer description: Number of live deals (the same set `GET /syndicates/{syndicate_id}/deals` returns) example: 3 primary_fund_company_id: type: integer nullable: true deprecated: true description: Internal integer id. Deprecated — use `primary_fund_company` (`co_...`) instead. example: 4242 primary_fund_company: type: string nullable: true description: The primary fund company's id (`co_...`), when the syndicate has one. example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY created_by_user_id: type: integer deprecated: true description: Internal integer id. Deprecated — use `created_by` (`usr_...`) instead. example: 123 created_by: type: string nullable: true description: The creating user's id (`usr_...`). example: usr_8Kd0aB3xQ9k2vF8mNp1zT5wY 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' SyndicateMember: type: object description: JSON:API resource representing a syndicate member (ClubRole) properties: id: type: string description: The member's id (`mem_...`); pass as member_id to the member endpoints. example: mem_3StSZhDjMHzn8NzEkX0kgEuN type: type: string example: syndicate_member attributes: type: object properties: user_id: type: integer nullable: true deprecated: true description: Internal integer id. Deprecated — use `user` (`usr_...`) instead. example: 123 user: type: string nullable: true description: The member's user id (`usr_...`); null for invitees who haven't signed up. example: usr_8Kd0aB3xQ9k2vF8mNp1zT5wY 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: integer nullable: true minimum: 1 maximum: 100 example: 20 description: Override carry percentage for this member carry_discount_percentage: type: number nullable: true description: Carry discount granted to this member, in percent. management_fee_discount_percentage: type: number nullable: true description: Management fee discount granted to this member, in percent. public_on_syndicate_profile: type: boolean description: Whether the member is listed on the public syndicate page. example: true interests: type: array items: type: string description: Investor interest tags from the user's profile. Empty for invitees. stage_preferences: type: array items: type: string description: Investment stage preference tags. Empty for invitees. looking_for: type: array items: type: string description: '''Looking for'' tags from the user''s profile. Empty for invitees.' linkedin_url: type: string nullable: true twitter_url: type: string nullable: true website: type: string nullable: true state: type: string nullable: true description: User's state or region. Omitted, with city and country, when the user hides their location. 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: '**Moderator-only.** Total invested in syndicate deals, in whole dollars, as a string.' example: '5000' 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: string description: The deal's offering id (`ofr_...`). Same id `get_offering` / `explore_offerings` use. example: ofr_yw3OhusvpfJP4Pk3wRYV1F2R type: type: string example: syndicate_deal attributes: type: object properties: name: type: string example: Acme Corp Series A status: type: string enum: - upcoming - open - closed - canceled description: Where the round is in its life (same values as an offering's `status`). example: open company_name: type: string example: Acme Corp company_id: type: integer deprecated: true description: Internal integer id. Deprecated — use `company` (`co_...`) instead. example: 1234 company: type: string nullable: true description: The company's id (`co_...`). example: co_8Kd0aB3xQ9k2vF8mNp1zT5wY 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 raised in whole dollars, as a string example: '230000' 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 (the one cents field on this object) example: '50000000' exemption: oneOf: - $ref: '#/components/schemas/Exemption' nullable: true description: The SEC exemption the deal is offered under, in market vocabulary. security: oneOf: - $ref: '#/components/schemas/Security' nullable: true description: What the deal issues, as a discriminated union on `type` (see `Security`). min_investment: type: string nullable: true description: Minimum investment in whole dollars, as a string example: '100' max_investment: type: string nullable: true description: Maximum investment in whole dollars, as a string example: '50000' 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' 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: security: nullable: true allOf: - $ref: '#/components/schemas/SecuritySummary' description: | What this tier issues, as `{ type, label }` (the terms are in `terms` below). May be null on legacy offerings — fall back to the position-level `security`. 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.' security: nullable: true allOf: - $ref: '#/components/schemas/SecuritySummary' description: What the round issued, as `{ type, label }`. Null when the round has no security recorded. 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: string description: The investor's id (`usr_...`) — one row per investor, aggregated across their investments in the deal. example: usr_aB3xQ9k2vF8mNp1zT5wY7Qc4 type: type: string example: deal_investor attributes: type: object properties: user_id: type: integer deprecated: true description: Internal integer id. Deprecated — use `user` (`usr_...`) instead. example: 456 user: type: string description: The investor's id (`usr_...`). example: usr_aB3xQ9k2vF8mNp1zT5wY7Qc4 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: Total invested in this deal, in whole dollars, as a string example: '5000' status: type: string enum: - pending - confirmed description: The investor's most advanced commitment in this deal — `confirmed` is final, `pending` is committed but not yet final. 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: string description: Investment id (`inv_...`). example: inv_8Kd0aB3xQ9k2vF8mNp1zT5wY type: type: string example: member_investment attributes: type: object properties: fundraise_id: type: integer deprecated: true description: Internal integer id. Deprecated — use `offering` (`ofr_...`) instead. example: 75496 offering: type: string nullable: true description: The deal's id (`ofr_...`), accepted by the deal endpoints. example: ofr_8Kd0aB3xQ9k2vF8mNp1zT5wY company_name: type: string nullable: true description: Name of the company the deal is for example: Substack amount: type: string description: Investment amount in whole dollars, as a string example: '5000' status: type: string enum: - pending - confirmed description: '`confirmed` is final; `pending` is committed but not yet final.' 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 whole dollars, as a string. example: '107800' 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 description: The intent's id (`int_...`). The `review_url` embeds the separate web (UUID) id. example: int_aB3xQ9k2vF8mNp1zT5wY7Qc4 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: oneOf: - type: string - type: integer nullable: true description: | For Club resources, the syndicate's id (`syn_...`). For every other `resource_type` (contract change plans, tranches, applications, ...) the resource's integer id, since those models have no external id yet. 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: string format: uuid description: Event id (UUID). example: 6f1c2d3e-4a5b-4c6d-8e9f-0a1b2c3d4e5f 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 nullable: true example: ChatGPT via Wefunder MCP action: type: string example: syndicates.member.invited resource_type: type: string example: Club resource_id: oneOf: - type: string - type: integer nullable: true description: | For Club resources, the syndicate's id (`syn_...`). For every other `resource_type` (contract change plans, tranches, applications, ...) the resource's integer id, since those models have no external id yet. 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 deprecated: true description: Legacy UUID of the associated intent. Deprecated — use `intent` (`int_...`) instead. intent: type: string nullable: true description: The associated intent's id (`int_...`) if this event was triggered by an intent. example: int_aB3xQ9k2vF8mNp1zT5wY7Qc4 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'