Skip to main content

Wefunder API Reference

Complete reference for the Wefunder API. Build integrations, track investments, and manage campaigns programmatically.

Showing
Base URLhttps://api.wefunder.com/api/v2

Authentication

The Wefunder API uses OAuth 2.0 Bearer tokens for authentication. Include your access token in the Authorization header of every request.

OAuth Scopes

read:publicView public deal data (no account needed)
read:exploreView the deals you can see on Wefunder
read:profileView your basic profile and the companies you follow
read:profile:followsView the companies you follow
write:profileUpdate your profile
write:followsFollow and unfollow companies
write:commentsDraft questions, answers, and comments for your approval
read:investmentsView investments and TTW reservations you can access
read:companiesView company info and offerings you can access, including the investor roster of a company you help run
read:invite_linksSee the investment links your app created and who used them
write:invite_linksCreate investment links to your offerings
read:investment_sessionsView hosted investment sessions for your offerings
write:investment_sessionsCreate and cancel hosted investment sessions
read:webhooksView your app's webhook endpoints and delivery history
write:webhooksCreate, update, and remove your app's webhook endpoints
read:installationsView where your app is installed
write:installationsInstall and uninstall your app on companies and syndicates you can edit
read:syndicatesView syndicate deals and members
write:syndicatesManage syndicate deals and members
write:syndicate_membersManage syndicate members
Authenticated Request
curl https://api.wefunder.com/api/v2/users/me \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": 123,
    "type": "user",
    "attributes": {
      "email": "[email protected]",
      "username": "investor123",
      "name": "Jane Doe"
    }
  }
}

Explore

GET/explore

List public offerings

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 and per_page. meta.total_count is the number of offerings across all pages, after filters.

Closed rounds (status). By default the list is live rounds. status=closed lists rounds that finished raising instead, newest closed_at first. It is not limited to one per company or to the curated explore set: it covers every finished round that GET /offerings/{id} serves to a logged-out visitor, for companies listed in Wefunder's public directory. status=all returns the live list first, then the closed list. With status=closed or status=all, exemption is the only filter accepted, and sort orders only the live part. Closed rounds follow the public view even for a read:explore token.

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
  • Bulk-list closed rounds (status=closed)

Parameters

string
per_pageintegerquery

Offerings per page.

statusstringquery

Which rounds to list:

  • live (default): rounds accepting investments or reservations, as on wefunder.com/explore
  • closed: rounds that finished raising, newest closed_at first (id breaks ties)
  • all: the live list, then the closed list

Any other value is a 400.

sortstringquery

Ordering of live rounds. A 400 with status=closed; with status=all it orders the live part only.

  • most_raised (default) — highest amount raised first
  • closing_soon — soonest funding close first
  • newest — most recently launched first
  • most_investors — highest investor count first
exemptionstringquery

Only offerings under this SEC exemption family. Reg CF and Reg D are the supported public filters.

securitystringquery

Only offerings issuing this kind of security — the security.type value on results.

testing_the_watersbooleanquery

false — only live rounds accepting investments; true — only Testing-the-Waters rounds collecting non-binding reservations.

max_min_investmentnumberquery

Only offerings whose minimum investment is at most this amount (USD).

closing_beforestringquery

Only offerings whose funding close date is on or before this date (ISO 8601). Offerings with no close date are excluded.

min_amount_raisednumberquery

Only offerings that have raised at least this amount so far (USD).

industryarrayquery

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.

business_modelarrayquery

Only companies with any of these business models (see each offering's business_models). One value or a comma-separated list.

followedbooleanquery

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.

Responses

200Successful response
400A filter or `status` value is out of vocabulary or malformed, or a live-only parameter
401
429
curl https://api.wefunder.com/api/v2/explore?per_page=value&status=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "ofr_yw3OhusvpfJP4Pk3wRYV1F2R",
      "type": "offering",
      "attributes": {
        "company_name": "ZeroBorder Self-Banking Centers",
        "tagline": "Example tagline",
        "url": "https://wefunder.com/zeroborder",
        "card_image_url": "https://uploads.wefunder.com/uploads/company/custom_card_photo/189517/large_card.jpg",
        "logo_url": "https://uploads.wefunder.com/uploads/company/logo/189517/large_logo.png",
        "exemption": {
          "family": "reg_cf",
          "subtype": null,
          "label": "Reg CF"
        },
        "security": null,
        "intended_security": {
          "type": "safe",
          "label": "SAFE"
        },
        "industries": [
          {
            "type": "robotics",
            "label": "Robotics"
          }
        ],
        "business_models": [
          {
            "type": "robotics",
            "label": "Robotics"
          }
        ],
        "status": "open",
        "testing_the_waters": false,
        "funding_target": "1000000",
        "funding_target_cents": 100000000,
        "max_funding_target": "1235000",
        "max_funding_target_cents": 123500000,
        "min_investment": "100",
        "min_investment_cents": 10000,
        "amount_raised": "642300",
        "amount_raised_cents": 64230000,
        "investor_count": 1203,
        "started_at": "2025-03-01T12:00:00Z",
        "closes_at": "2025-04-30T03:59:59Z",
        "closed_at": "2025-03-01T12:00:00Z",
        "form_c_url": "https://www.sec.gov/Archives/edgar/data/1834121/000167025425000123/0001670254-25-000123-index.htm",
        "spv_legal_name": "WF ZeroBorder 2025, a series of Wefunder SPV, LLC",
        "publicly_visible": true,
        "invested": false,
        "followed": false
      },
      "company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "warnings": [
        {
          "code": "prior_rounds",
          "message": "This company completed 2 earlier rounds on Wefunder. amount_raised and investor_count cover this offering only, not the company's lifetime total."
        }
      ],
      "etag": "a1b2c3d4e5f6"
    }
  ],
  "meta": {
    "total_count": 119,
    "page_count": 25,
    "has_more": true,
    "status": "live",
    "sort": "most_raised",
    "personalized": false,
    "filters": {
      "security": "safe",
      "testing_the_waters": false
    },
    "next_cursor": 25
  }
}
GET/offerings/{external_id}

Get a public offering

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.

Parameters

external_idstringrequiredpath

The offering's id (ofr_...).

Responses

200Successful response
401
404
429
curl https://api.wefunder.com/api/v2/offerings/ofr_yw3OhusvpfJP4Pk3wRYV1F2R \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "ofr_yw3OhusvpfJP4Pk3wRYV1F2R",
    "type": "offering",
    "attributes": {
      "company_name": "ZeroBorder Self-Banking Centers",
      "tagline": "Example tagline",
      "url": "https://wefunder.com/zeroborder",
      "card_image_url": "https://uploads.wefunder.com/uploads/company/custom_card_photo/189517/large_card.jpg",
      "logo_url": "https://uploads.wefunder.com/uploads/company/logo/189517/large_logo.png",
      "exemption": {
        "family": "reg_cf",
        "subtype": null,
        "label": "Reg CF"
      },
      "security": null,
      "intended_security": {
        "type": "safe",
        "label": "SAFE"
      },
      "industries": [
        {
          "type": "robotics",
          "label": "Robotics"
        }
      ],
      "business_models": [
        {
          "type": "robotics",
          "label": "Robotics"
        }
      ],
      "status": "open",
      "testing_the_waters": false,
      "funding_target": "1000000",
      "funding_target_cents": 100000000,
      "max_funding_target": "1235000",
      "max_funding_target_cents": 123500000,
      "min_investment": "100",
      "min_investment_cents": 10000,
      "amount_raised": "642300",
      "amount_raised_cents": 64230000,
      "investor_count": 1203,
      "started_at": "2025-03-01T12:00:00Z",
      "closes_at": "2025-04-30T03:59:59Z",
      "closed_at": "2025-03-01T12:00:00Z",
      "form_c_url": "https://www.sec.gov/Archives/edgar/data/1834121/000167025425000123/0001670254-25-000123-index.htm",
      "spv_legal_name": "WF ZeroBorder 2025, a series of Wefunder SPV, LLC",
      "publicly_visible": true,
      "invested": false,
      "followed": false
    },
    "company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
    "warnings": [
      {
        "code": "prior_rounds",
        "message": "This company completed 2 earlier rounds on Wefunder. amount_raised and investor_count cover this offering only, not the company's lifetime total."
      }
    ],
    "etag": "a1b2c3d4e5f6"
  }
}
GET/companies/{id}/disclosures

Get a company's public Form C disclosures

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 decimal strings, never USD by assumption: the round's own figures (use of funds, prior offerings, notes, debts, related parties) are in currency — EUR for an ECSP round — while the Form C financial statements, the ratio derived from them, and the current-position figures (which repeat it as current_position.currency) are in financials_currency, the currency the issuer declared for its statements (it can differ from the round's: DKK, RON, GBP, or USD statements behind a EUR round). Ratios are percentages as decimal strings. Everything here is the issuer's own disclosure, served as filed, not a Wefunder assessment.

Parameters

idstringrequiredpath
sectionsarrayquery

Only these sections (offering_id, currency, financials_currency 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.

Responses

200Successful response
400`sections` names a section that does not exist.
401
404Unknown or hidden company (`error.type` = `not_found`), or no public Form C disclosures (`error.type` = `no_disclosures`).
429
curl https://api.wefunder.com/api/v2/companies/co_8Kd0aB3xQ9k2vF8mNp1zT5wY/disclosures?sections=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "company_disclosures",
    "attributes": {
      "offering_id": "string",
      "currency": "USD",
      "financials_currency": "USD",
      "filing": {
        "exemption": {
          "family": "reg_cf",
          "subtype": null,
          "label": "Reg CF"
        },
        "sec_filing_url": "string",
        "filed": true,
        "statement_date": "string",
        "fiscal_year_end": "string"
      },
      "business": {
        "legal_name": "string",
        "legal_form": "string",
        "jurisdiction": "string",
        "incorporated_on": "string",
        "employees": 123
      },
      "financial_statements": {
        "most_recent": {
          "total_assets": "string",
          "cash_and_equivalents": "string",
          "accounts_receivable": "string",
          "current_liabilities": "string",
          "non_current_liabilities": "string",
          "short_term_debt": "string",
          "long_term_debt": "string",
          "revenue": "string",
          "cost_of_goods_sold": "string",
          "taxes_paid": "string",
          "net_income": "string"
        },
        "prior": {
          "total_assets": "string",
          "cash_and_equivalents": "string",
          "accounts_receivable": "string",
          "current_liabilities": "string",
          "non_current_liabilities": "string",
          "short_term_debt": "string",
          "long_term_debt": "string",
          "revenue": "string",
          "cost_of_goods_sold": "string",
          "taxes_paid": "string",
          "net_income": "string"
        },
        "prior_prior": {
          "total_assets": "string",
          "cash_and_equivalents": "string",
          "accounts_receivable": "string",
          "current_liabilities": "string",
          "non_current_liabilities": "string",
          "short_term_debt": "string",
          "long_term_debt": "string",
          "revenue": "string",
          "cost_of_goods_sold": "string",
          "taxes_paid": "string",
          "net_income": "string"
        }
      },
      "ratios": {
        "net_margin_percent": "string",
        "gross_margin_percent": "string",
        "return_on_assets_percent": "string",
        "liabilities_to_assets_percent": "string",
        "debt_to_assets_percent": "string",
        "cash_to_assets_percent": "string",
        "revenue_per_employee": "string"
      },
      "current_position": {
        "currency": "DKK",
        "cash_on_hand": "string",
        "cash_on_hand_as_of": "string",
        "average_monthly_revenue": "string",
        "average_monthly_cost_of_goods": "string",
        "average_monthly_expenses": "string",
        "average_monthly_burn": "string"
      },
      "financial_condition": "string",
      "financial_statement_documents": [
        {
          "name": "string",
          "url": "string"
        }
      ],
      "investment_documents": [
        {
          "name": "string",
          "url": "string"
        }
      ],
      "risks": [
        "string"
      ],
      "use_of_funds": [
        {
          "if_raised": "string",
          "plan": "string"
        }
      ],
      "directors": [
        {
          "name": "string",
          "titles": "string",
          "since": 123,
          "director": true,
          "officer": true
        }
      ],
      "officers": [
        {
          "name": "string",
          "titles": "string",
          "since": 123,
          "director": true,
          "officer": true
        }
      ],
      "voting_power": [
        {
          "name": "string",
          "holding": "string",
          "voting_power_percent": "string"
        }
      ],
      "capital_structure": [
        {
          "class_of_security": "string",
          "authorized": "string",
          "outstanding": "string",
          "voting_rights": "string",
          "other_rights": "string"
        }
      ],
      "prior_offerings": [
        {
          "date": "string",
          "exemption": "string",
          "security_type": "string",
          "amount_sold": "string",
          "use_of_proceeds": "string",
          "offering_id": "string"
        }
      ],
      "outstanding_notes": [
        {
          "amount": "string",
          "outstanding_principal": "string",
          "valuation_cap": "string",
          "uncapped": true,
          "interest_rate_percent": "string",
          "discount_percent": "string",
          "maturity_date": "string",
          "description": "string"
        }
      ],
      "outstanding_debts": {
        "note": "string",
        "items": [
          {
            "original_amount": "string",
            "outstanding_principal": "string",
            "current_with_payments": true,
            "maturity_date": "string",
            "description": "string"
          }
        ]
      },
      "related_parties": {
        "description": "string",
        "parties": [
          {
            "name": "string",
            "relationship": "string",
            "amount": "string",
            "date": "string",
            "outstanding_principal": "string",
            "description": "string"
          }
        ]
      }
    }
  }
}
GET/companies/{id}/pitch

Get a company's pitch

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.

Parameters

idstringrequiredpath
sectionsarrayquery

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.

Responses

200Successful response
400`sections` names a section that does not exist.
401
404Unknown or hidden company.
429
curl https://api.wefunder.com/api/v2/companies/co_8Kd0aB3xQ9k2vF8mNp1zT5wY/pitch?sections=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "company_pitch",
    "attributes": {
      "offering_id": "string",
      "title": "string",
      "url": "string",
      "authored_by": "company",
      "disclaimer": "string",
      "story": {
        "blocks": [
          {
            "type": "heading",
            "level": 123,
            "text": "string",
            "number": 123,
            "ordered": true,
            "start": 123,
            "items": [
              "string"
            ],
            "links": [
              {
                "text": "string",
                "url": "string"
              }
            ],
            "url": "string",
            "link": "string",
            "alt": "string",
            "filename": "string",
            "provider": "string"
          }
        ],
        "image_count": 123,
        "video_count": 123,
        "inline_images_omitted": 123,
        "character_count": 123
      },
      "perks": {
        "offering_id": "string",
        "currency": "string",
        "described_in_pitch": true,
        "tiers": [
          {
            "qualifying_amount": "string",
            "description": "string"
          }
        ]
      }
    }
  }
}
GET/companies/{id}/updates

List a company's posts

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.

Parameters

idstringrequiredpath
cursorstringquery
per_pageintegerquery

Responses

200Successful response
401
404Unknown company, or one the site does not show this viewer.
429
curl https://api.wefunder.com/api/v2/companies/co_8Kd0aB3xQ9k2vF8mNp1zT5wY/updates?cursor=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "string",
      "type": "company_update",
      "attributes": {
        "kind": "string",
        "title": "string",
        "excerpt": "string",
        "content": "string",
        "published_at": "string",
        "pinned": true,
        "visibility": "public",
        "author": {
          "name": "string"
        },
        "comments_count": 123,
        "likes_count": 123,
        "url": "string"
      }
    }
  ],
  "meta": {
    "company": "string",
    "has_more": true,
    "page_count": 123,
    "next_cursor": 123
  }
}
GET/companies/{id}/updates/{update_id}

Get one company post in full

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.

Parameters

idstringrequiredpath
update_idstringrequiredpath

Responses

200Successful response
401
404Unknown company or post, or one the site does not show this viewer.
429
curl https://api.wefunder.com/api/v2/companies/{id}/updates/{update_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "company_update",
    "attributes": {
      "kind": "string",
      "title": "string",
      "excerpt": "string",
      "content": "string",
      "published_at": "string",
      "pinned": true,
      "visibility": "public",
      "author": {
        "name": "string"
      },
      "comments_count": 123,
      "likes_count": 123,
      "url": "string"
    }
  }
}
GET/companies/{id}/questions

List a company's investor questions and answers

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).

Parameters

idstringrequiredpath
sortstringquery
past_raisesbooleanquery
qstringquery

Full-text search over questions and answers. At most 200 bytes.

unanswered_by_teambooleanquery
cursorstringquery
per_pageintegerquery

Responses

200Successful response
400`sort` is not one of the tab's options, or `q` is over 200 bytes.
401
404Unknown company, or one the site does not show this viewer.
429
curl https://api.wefunder.com/api/v2/companies/co_8Kd0aB3xQ9k2vF8mNp1zT5wY/questions?sort=value&past_raises=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "cmt_m2JkUx7E3aBFscGMKDdJ9saR",
      "type": "company_question",
      "attributes": {
        "question": "string",
        "asked_at": "string",
        "asked_by": {
          "id": "usr_7Hk2mPq9rT4vX1yB3nC5dF8g",
          "name": "string",
          "role": "string",
          "team": true
        },
        "likes_count": 123,
        "highlighted": true,
        "answered_by_team": true,
        "match": "question",
        "answers": [
          {
            "id": "string",
            "answer": "string",
            "answered_at": "string",
            "answered_by": {
              "id": "usr_7Hk2mPq9rT4vX1yB3nC5dF8g",
              "name": "string",
              "role": "string",
              "team": true
            },
            "likes_count": 123
          }
        ],
        "url": "string"
      }
    }
  ],
  "meta": {
    "company": "string",
    "sort": "string",
    "q": "string",
    "unanswered_by_team": true,
    "past_raises": true,
    "questions_since": "string",
    "total": 123,
    "has_more": true,
    "page_count": 123,
    "next_cursor": 123
  }
}
GET/companies/{id}

Get a company page

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 its top-level company field (a bare string).

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.

Parameters

idstringrequiredpath

The company's id (co_...).

Responses

200Successful response
401
404Unknown id, or a company the viewer may not see.
429
curl https://api.wefunder.com/api/v2/companies/co_8Kd0aB3xQ9k2vF8mNp1zT5wY \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
    "type": "company",
    "attributes": {
      "name": "string",
      "tagline": "string",
      "url": "string",
      "logo_url": "string",
      "card_image_url": "string",
      "location": {
        "city": "string",
        "state": "string",
        "country": "string"
      },
      "legal_name": "Acme Robotics, Inc.",
      "legal_form": "Corporation",
      "website_url": "https://acme.example",
      "twitter_url": "https://twitter.com/acme",
      "linkedin_url": "https://linkedin.com/company/acme-robotics",
      "facebook_url": "https://facebook.com/acme",
      "instagram_url": "https://instagram.com/acme",
      "promotion_opt_in": true,
      "raising": true,
      "current_raise": null,
      "past_rounds": [
        {
          "offering_id": "string",
          "source": "wefunder",
          "exemption": {
            "family": "reg_cf",
            "subtype": null,
            "label": "Reg CF"
          },
          "amount_raised": "string",
          "amount_raised_cents": 123,
          "investor_count": 123,
          "opened_at": "string",
          "closed_at": "string"
        }
      ],
      "wefunder_rounds": [
        {
          "offering_id": "string",
          "current": true,
          "status": "open",
          "exemption": null,
          "security": null,
          "amount_raised": "string",
          "amount_raised_cents": 123,
          "investor_count": 123,
          "opened_at": "string",
          "closed_at": "string"
        }
      ],
      "totals": {
        "raised_on_wefunder_all_time": "string",
        "raised_on_wefunder_all_time_cents": 123,
        "profile_total_raised": "string",
        "profile_total_raised_cents": 123,
        "profile_reported_off_platform": "string",
        "profile_reported_off_platform_cents": 123,
        "profile_includes_past_rounds": true
      }
    }
  }
}

Companies

GET/companies/search

Search companies by name

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.

Parameters

qstringrequiredquery

The company name, or part of it. At most 200 characters and 500 bytes.

limitintegerquery

Maximum results, 1–20. Default 8, the same as the site.

Responses

200Successful response
400`q` is missing, blank, or over 200 characters / 500 bytes.
401
403The token holds neither `read:public` nor `read:explore`.
429
503The search provider failed; retry. Not "no results".
curl https://api.wefunder.com/api/v2/companies/search?q=value&limit=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "type": "company",
      "attributes": {
        "name": "Acme Robotics",
        "tagline": "string",
        "url": "https://wefunder.com/acme",
        "logo_url": "string",
        "raising": true,
        "profile_available": true
      }
    }
  ],
  "meta": {
    "query": "string",
    "count": 123
  }
}

Users

GET/users/me

Get current user profile

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.

Two shapes, depending on the token:

  • With read:profile (or write:profile): the full profile shown in the example.
  • Any other user token: only the user's id and type, {"data": {"id": "usr_...", "type": "user"}}, with no attributes. Use it to learn whose token you hold; it never includes the email, name, username or city.

A token with no user (a server-side client_credentials key, or an installation acting as a company or syndicate) gets 403. Before this change a user token without read:profile got 403 here too; code that read attributes unconditionally should now check for it.

Responses

200Successful response
401
429
curl https://api.wefunder.com/api/v2/users/me \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
    "type": "user",
    "attributes": {
      "email": "[email protected]",
      "username": "john_doe",
      "name": "John",
      "full_name": "John Doe",
      "bio": "Entrepreneur and investor",
      "city": "San Francisco",
      "country": "USA",
      "avatar_url": "https://example.com/avatar.jpg",
      "created_at": "2023-01-15T10:30:00Z",
      "certified_at": "2023-02-01T14:20:00Z"
    }
  }
}
GET/users/me/companies

List the companies you edit

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, ...).

Responses

200Successful response
401
429
curl https://api.wefunder.com/api/v2/users/me/companies \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "type": "company",
      "attributes": {
        "name": "Acme Robotics",
        "tagline": "string",
        "url": "https://wefunder.com/acme",
        "roles": [
          "founder"
        ],
        "raising": true
      }
    }
  ],
  "meta": {
    "count": 123
  }
}
GET/users/me/follows

List the companies you follow

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.

Accepts read:profile or write:follows. Both include read:profile:follows, the follows list on its own, which is what this endpoint checks. A token holding only write:follows can list its follows here but cannot read the profile (GET /users/me).

Which companies are listed depends on the grant, the same way the follow list on a user's public Wefunder profile does:

  • write:follows, read:mcp (or the retired read:follows): the full list, every company the user follows. An app that follows and unfollows for the user, or the user's own AI assistant acting as them, sees what the user sees.
  • read:profile or write:profile without one of those: the public list. A company that has asked not to be promoted (promotion_opt_in false on GET /companies/{id}) is left out, exactly as it is left out of the profile's follow list for every visitor but the user. This holds for every user, admins included.

The filter is applied before paging, so it never shortens a page, and page_count counts the rows returned.

Parameters

cursorstringquery
per_pageintegerquery

Responses

200Successful response
401
429
curl https://api.wefunder.com/api/v2/users/me/follows?cursor=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "type": "company",
      "attributes": {
        "name": "string",
        "tagline": "string",
        "url": "string",
        "logo_url": "string",
        "raising": true,
        "profile_available": true,
        "followed_at": "string"
      }
    }
  ],
  "meta": {
    "page_count": 25,
    "has_more": true,
    "next_cursor": 12345
  }
}
PUT/users/me/follows/{company_id}

Follow a company

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.

Parameters

company_idstringrequiredpath

Responses

200The follow state after the call.
401
403The token does not hold `write:follows`.
404Unknown company, or one the site does not show this user.
429
curl -X PUT https://api.wefunder.com/api/v2/users/me/follows/co_8Kd0aB3xQ9k2vF8mNp1zT5wY \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "follow_state",
    "attributes": {
      "followed": true,
      "changed": true
    }
  }
}
DELETE/users/me/follows/{company_id}

Unfollow a company

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.

Parameters

company_idstringrequiredpath

Responses

200The follow state after the call.
401
403The token does not hold `write:follows`.
404Unknown company.
429
curl -X DELETE https://api.wefunder.com/api/v2/users/me/follows/co_8Kd0aB3xQ9k2vF8mNp1zT5wY \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "follow_state",
    "attributes": {
      "followed": true,
      "changed": true
    }
  }
}

Investments

GET/investments

List or sync investments

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 included (visible: false with id, observed_at, cursor and reason, and no payload fields). Apply in cursor order; always store meta.next_cursor. Filters are not encoded in the cursor: repeat the same company_id, offering_id or investor_id on every sync request, or the sync widens to the whole audience.
  • Sync by time: updated_since=<ISO 8601> 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).

Parameters

company_idstringquery

Company external id (co_…).

offering_idstringquery

Offering external id (ofr_…). In sync mode an investment that moved to another offering is delivered once more as a tombstone under this filter.

investor_idstringquery

Investor external id (usr_…).

statusstringquery

List mode only.

updated_sincestringquery

ISO 8601 timestamp; switches to sync mode. Mutually exclusive with cursor (cursor wins).

cursorstringquery

Opaque cursor from a previous response's meta.next_cursor.

per_pageintegerquery

Records per page, 1–100 (default 25). Sync pages count distinct investments.

Responses

200A page of records.
400Malformed cursor or `updated_since`, unknown `status`, or `status` combined with sync.
401
404Unknown `company_id`, `offering_id` or `investor_id`.
410The cursor or `updated_since` is older than the retention window. List again (no
429
curl https://api.wefunder.com/api/v2/investments?company_id=value&offering_id=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "string",
      "visible": true,
      "status": "reserved",
      "converted_to": "string",
      "converted_from": "string",
      "offering": "string",
      "company": "string",
      "investment_type": "string",
      "offering_type": "string",
      "group": "string",
      "applied_at": "string",
      "amounts": {
        "committed_cents": 123,
        "investment_size_cents": 123,
        "in_escrow_cents": 123,
        "raised_cents": 123,
        "currency": "usd"
      },
      "shares": "295",
      "average_share_price": "3.38",
      "needs_whitelisting": true,
      "external_username": "string",
      "message": "string",
      "investor": {
        "id": "string",
        "deactivated": true,
        "via_entity": true,
        "name": "string",
        "legal_name": "string",
        "email": "string",
        "address": null,
        "bio": "string"
      },
      "blockers": [
        {
          "key": "string",
          "description": "string"
        }
      ],
      "contracts": [
        {
          "name": "string",
          "override_amount_cents": 123,
          "early_bird": true
        }
      ],
      "observed_at": "string",
      "cursor": "string",
      "reason": "changed"
    }
  ],
  "meta": {
    "mode": "bootstrap",
    "has_more": true,
    "next_cursor": "string",
    "page_count": 123,
    "published_through": "string"
  }
}
GET/investments/{id}

Retrieve an investment (current)

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.

Parameters

idstringrequiredpath

Investment external id (inv_…).

Responses

200The current record.
401
404Not found, not in the token's audience, or not visible to it.
429
curl https://api.wefunder.com/api/v2/investments/{id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "visible": true,
    "status": "reserved",
    "converted_to": "string",
    "converted_from": "string",
    "offering": "string",
    "company": "string",
    "investment_type": "string",
    "offering_type": "string",
    "group": "string",
    "applied_at": "string",
    "amounts": {
      "committed_cents": 123,
      "investment_size_cents": 123,
      "in_escrow_cents": 123,
      "raised_cents": 123,
      "currency": "usd"
    },
    "shares": "295",
    "average_share_price": "3.38",
    "needs_whitelisting": true,
    "external_username": "string",
    "message": "string",
    "investor": {
      "id": "string",
      "deactivated": true,
      "via_entity": true,
      "name": "string",
      "legal_name": "string",
      "email": "string",
      "address": null,
      "bio": "string"
    },
    "blockers": [
      {
        "key": "string",
        "description": "string"
      }
    ],
    "contracts": [
      {
        "name": "string",
        "override_amount_cents": 123,
        "early_bird": true
      }
    ],
    "observed_at": "string",
    "cursor": "string",
    "reason": "changed"
  },
  "meta": {
    "source": "current",
    "published_observed_at": "string",
    "published_matches": true
  }
}
GET/offerings/{offering_id}/stats

Investment totals for an offering

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.

Parameters

offering_idstringrequiredpath

Offering external id (ofr_…).

Responses

200Totals by status.
401
403The token's user does not edit the offering's company.
404Unknown offering.
429
curl https://api.wefunder.com/api/v2/offerings/{offering_id}/stats \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "offering": "string",
    "company": "string",
    "currency": "string",
    "by_status": null,
    "total": {
      "count": 123,
      "committed_cents": 123,
      "raised_cents": 123
    }
  },
  "meta": {
    "source": "published",
    "published_through": "string"
  }
}

Investment Delta

GET/companies/{company_id}/investments/changes

Legacy alias: bootstrap or page investment changes for a company

Compatibility alias. This path keeps working unchanged for partners already on it, but new integrations should use List or sync investments (GET /investments?company_id=…): same records, interchangeable cursors. The migration notes list the differences.

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.

Parameters

company_idstringrequiredpath

The company's external id (co_…).

cursorstringquery

Opaque cursor from a previous response's meta.next_cursor. Omit to bootstrap.

per_pageintegerquery

Records per page, 1–100 (default 25). Delta pages count distinct investments.

Responses

200A page of records.
400Malformed cursor.
401
403Token lacks both `read:investments` and `read:companies`, or the user cannot edit this company.
410The cursor is older than the retention window. Bootstrap again (no cursor) and replace
429
curl https://api.wefunder.com/api/v2/companies/{company_id}/investments/changes?cursor=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "string",
      "visible": true,
      "status": "reserved",
      "converted_to": "string",
      "converted_from": "string",
      "offering": "string",
      "company": "string",
      "investment_type": "string",
      "offering_type": "string",
      "group": "string",
      "applied_at": "string",
      "amounts": {
        "committed_cents": 123,
        "investment_size_cents": 123,
        "in_escrow_cents": 123,
        "raised_cents": 123,
        "currency": "usd"
      },
      "shares": "295",
      "average_share_price": "3.38",
      "needs_whitelisting": true,
      "external_username": "string",
      "message": "string",
      "investor": {
        "id": "string",
        "deactivated": true,
        "via_entity": true,
        "name": "string",
        "legal_name": "string",
        "email": "string",
        "address": null,
        "bio": "string"
      },
      "blockers": [
        {
          "key": "string",
          "description": "string"
        }
      ],
      "contracts": [
        {
          "name": "string",
          "override_amount_cents": 123,
          "early_bird": true
        }
      ],
      "observed_at": "string",
      "cursor": "string",
      "reason": "changed"
    }
  ],
  "meta": {
    "mode": "bootstrap",
    "has_more": true,
    "next_cursor": "string",
    "page_count": 123,
    "company_id": "string"
  }
}

Portfolio

GET/portfolio

Get portfolio summary

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.

Parameters

statusstringquery

Total only positions with this status.

companystringquery

Total only positions in this company, by its id (co_...). Unknown ids return 404.

Responses

200Successful response
401
403
429
curl https://api.wefunder.com/api/v2/portfolio?status=value&company=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "type": "portfolio_summary",
    "attributes": {
      "currency": "usd",
      "other_currencies": [],
      "total_cost_basis_cents": 123,
      "total_current_value_cents": 123,
      "total_unrealized_gain_cents": 123,
      "total_realized_gain_cents": 123,
      "return_multiple": "1.3107",
      "position_counts": {
        "active": 123,
        "exited": 123,
        "sold": 123,
        "failed": 123
      },
      "as_of": "string"
    }
  }
}
GET/portfolio/positions

List portfolio positions

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.

Mind the units when deriving an average cost per share: cost_basis_cents is integer cents, while shares_held, current_share_price and terms.share_price are decimal strings in major units of the position's currency. Use decimal arithmetic and divide the cents by 100 first: cost_basis_cents / 100 / shares_held. For example, 215000 cents over "200.0" shares is 1075 cents per share, or 10.75 USD per share, which is what to compare with a current_share_price of "14.4632". Dividing cost_basis_cents by shares_held without the / 100 yields cents per share, 100 times the prices shown. When shares_held is null or zero (a non-share security, or a valuation that is not per-share) there is no average cost per share: report it as unavailable instead of treating the missing share count as a number.

Requires a user-authorized token, like GET /portfolio.

Parameters

string
per_pageintegerquery

Page size, 1-100. Defaults to 25.

statusstringquery

Filter positions by status.

companystringquery

Filter to a single company by its id (co_...). Unknown ids return 404.

Responses

200Successful response
400Invalid filter value (unknown status or malformed cursor)
401
403
404Filtered company not found
429
curl https://api.wefunder.com/api/v2/portfolio/positions?per_page=value&status=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "ofr_9aKxQ2vF8mNp1zT5wY7Qb3Cd",
      "type": "portfolio_position",
      "attributes": {
        "offering_id": "ofr_9aKxQ2vF8mNp1zT5wY7Qb3Cd",
        "company": {
          "id": "co_2bt9eJn6X8C9gnShl63qlzkK",
          "name": "Acme Robotics",
          "slug": "acme-robotics",
          "url": "https://wefunder.com/acme-robotics"
        },
        "asset_type": "company",
        "security": null,
        "status": "active",
        "exit_reason": "ipo",
        "exited_on": "2026-08-15",
        "invested_at": "string",
        "currency": "usd",
        "cost_basis_cents": 123,
        "current_value_cents": 123,
        "unrealized_gain_cents": 123,
        "realized_gain_cents": 123,
        "return_multiple": "1.6492",
        "shares_held": "string",
        "current_share_price": "string",
        "securities": [
          {
            "security": null,
            "early_bird": true,
            "terms": {
              "share_price": "10.0",
              "currency": "usd",
              "note_cap_cents": 123,
              "uncapped": false,
              "note_discount": "20.0",
              "premoney_valuation_cents": 123
            },
            "cost_basis_cents": 123,
            "current_value_cents": 123,
            "unrealized_gain_cents": 123,
            "realized_gain_cents": 123,
            "shares_held": "string",
            "current_share_price": "string",
            "invested_at": "string",
            "owner": {
              "kind": "individual",
              "name": "string"
            },
            "investor_count": 123
          }
        ],
        "holdings": [
          {
            "company": {
              "id": "co_2bt9eJn6X8C9gnShl63qlzkK",
              "name": "Acme Robotics",
              "slug": "acme-robotics",
              "url": "https://wefunder.com/acme-robotics"
            },
            "status": "active"
          }
        ],
        "investor_count": 123,
        "as_of": "string"
      }
    }
  ],
  "meta": {
    "has_more": true,
    "page_count": 123,
    "next_cursor": 123
  }
}

Campaigns

GET/campaigns

List founder campaigns

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.

Parameters

string

Responses

200Successful response
401
403
429
curl https://api.wefunder.com/api/v2/campaigns \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "ofr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "type": "campaign",
      "attributes": {
        "state": "active",
        "company_id": 101,
        "company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
        "company_name": "My Startup Inc.",
        "company_url": "my-startup-inc",
        "created_at": "2023-01-10T09:00:00Z",
        "updated_at": "2023-03-15T14:30:00Z",
        "closed_at": "2023-06-30T23:59:59Z",
        "amount_raised": 25000,
        "investor_count": 42
      }
    }
  ],
  "meta": {
    "page_count": 25,
    "has_more": true,
    "next_cursor": 12345
  }
}

Syndicates

GET/syndicates

List syndicates

Returns syndicates the authenticated user can manage, based on their roles. Results can be filtered by status and sorted by name or creation date.

Parameters

cursorstringquery

Opaque cursor from the previous response's meta.next_cursor.

per_pageintegerquery

Results per page, 1-100. Defaults to 25.

Responses

200Successful response
401
429
curl https://api.wefunder.com/api/v2/syndicates?cursor=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
      "type": "syndicate",
      "attributes": {
        "name": "Acme Syndicate",
        "slug": "acme-syndicate",
        "tagline": "Investing in the future of AI",
        "description": "Example text",
        "avatar_url": "https://uploads.wefunder.com/uploads/club/icon/42/large_avatar.png",
        "published": true,
        "launched": true,
        "membership_open": true,
        "syndicate_type": "string",
        "auto_accept_accredited_members": false,
        "auto_approve_join_sessions": true,
        "fund_required": false,
        "annual_subscription": {
          "fund_investment_amount": "string",
          "deals_per_year": 123,
          "amount_per_deal": "string"
        },
        "traditional_fund": {
          "require_minimum_investment": true,
          "minimum_investment_amount": "string"
        },
        "member_count": 47,
        "deal_count": 3,
        "primary_fund_company_id": 4242,
        "primary_fund_company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
        "created_by_user_id": 123,
        "created_by": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
        "created_at": "2025-03-01T12:00:00Z",
        "updated_at": "2025-03-01T12:00:00Z"
      }
    }
  ],
  "meta": {
    "page_count": 25,
    "has_more": true,
    "next_cursor": 12345
  }
}
GET/syndicates/{syndicate_id}

Get syndicate details

Returns full details for a specific syndicate including settings, team configuration, and summary metrics.

Parameters

string

Responses

200Successful response
401
403
404Syndicate not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
    "type": "syndicate",
    "attributes": {
      "name": "Acme Syndicate",
      "slug": "acme-syndicate",
      "tagline": "Investing in the future of AI",
      "description": "Example text",
      "avatar_url": "https://uploads.wefunder.com/uploads/club/icon/42/large_avatar.png",
      "published": true,
      "launched": true,
      "membership_open": true,
      "syndicate_type": "string",
      "auto_accept_accredited_members": false,
      "auto_approve_join_sessions": true,
      "fund_required": false,
      "annual_subscription": {
        "fund_investment_amount": "string",
        "deals_per_year": 123,
        "amount_per_deal": "string"
      },
      "traditional_fund": {
        "require_minimum_investment": true,
        "minimum_investment_amount": "string"
      },
      "member_count": 47,
      "deal_count": 3,
      "primary_fund_company_id": 4242,
      "primary_fund_company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "created_by_user_id": 123,
      "created_by": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
PATCH/syndicates/{syndicate_id}Beta

Update syndicate settings

Update syndicate configuration. Currently supports name, description, and tagline. Requires write:syndicates scope and at least operator permission.

Parameters

string

Request Body

syndicateobjectrequired

Responses

200Syndicate updated
401
403
422Validation error
curl -X PATCH https://api.wefunder.com/api/v2/syndicates/{syndicate_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Response200 OK
{
  "data": {
    "id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
    "type": "syndicate",
    "attributes": {
      "name": "Acme Syndicate",
      "slug": "acme-syndicate",
      "tagline": "Investing in the future of AI",
      "description": "Example text",
      "avatar_url": "https://uploads.wefunder.com/uploads/club/icon/42/large_avatar.png",
      "published": true,
      "launched": true,
      "membership_open": true,
      "syndicate_type": "string",
      "auto_accept_accredited_members": false,
      "auto_approve_join_sessions": true,
      "fund_required": false,
      "annual_subscription": {
        "fund_investment_amount": "string",
        "deals_per_year": 123,
        "amount_per_deal": "string"
      },
      "traditional_fund": {
        "require_minimum_investment": true,
        "minimum_investment_amount": "string"
      },
      "member_count": 47,
      "deal_count": 3,
      "primary_fund_company_id": 4242,
      "primary_fund_company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "created_by_user_id": 123,
      "created_by": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}

Syndicate Members

GET/syndicates/{syndicate_id}/members

List members

Returns the member directory for a syndicate with role, status, tags, sidebar notes, and investment history summaries. Supports filtering, sorting, search, and pagination.

Parameters

string
rolestringquery

Filter by role (e.g. creator, manager, member, invitee, applicant)

permissionstringquery

Filter by syndicate permission level

searchstringquery

Search by name or email

sortstringquery

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.

has_investedstringquery

Filter by whether the member has invested in any syndicate deal

accreditedstringquery

Moderator-only. Filter by accreditation status; ignored for non-moderators.

tagstringquery

Filter by 'can help with' tag (ILIKE match)

locationstringquery

Match on city, state, or country (public location data only).

cursorstringquery

Opaque cursor from the previous response's meta.next_cursor.

per_pageintegerquery

Results per page, 1-100. Defaults to 25.

Responses

200Successful response
401
403
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members?role=value&permission=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
      "type": "syndicate_member",
      "attributes": {
        "user_id": 123,
        "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
        "user_name": "Jane Smith",
        "user_email": "[email protected]",
        "role": "member",
        "invite_role": "admin",
        "syndicate_permission": "full_access",
        "title": "Example title",
        "sort_order": 5,
        "carry_percentage_override": 20,
        "carry_discount_percentage": 0,
        "management_fee_discount_percentage": 0,
        "public_on_syndicate_profile": true,
        "interests": [
          "string"
        ],
        "stage_preferences": [
          "string"
        ],
        "looking_for": [
          "string"
        ],
        "linkedin_url": "string",
        "twitter_url": "string",
        "website": "string",
        "state": "string",
        "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
        "bio": "Example text",
        "city": "San Francisco",
        "country": "US",
        "last_activity_at": "2025-03-01T12:00:00Z",
        "profile_url": "/janedoe",
        "tags": [
          "Fundraising",
          "Product Strategy"
        ],
        "joined_at": "2025-03-01T12:00:00Z",
        "investment_total": "5000",
        "deal_count": 2,
        "accredited": true,
        "legal_name": "Example Name",
        "starred": true,
        "private_tags": [
          "VIP",
          "Follow up"
        ],
        "created_at": "2025-03-01T12:00:00Z",
        "updated_at": "2025-03-01T12:00:00Z"
      }
    }
  ],
  "meta": {
    "page_count": 25,
    "has_more": true,
    "next_cursor": 12345
  }
}
POST/syndicates/{syndicate_id}/members/invite

Invite member

Send an invitation to join the syndicate by email.

Parameters

string

Request Body

emailstringrequired
namestring

Display name for the invitee

syndicate_permissionstring

Permission level. Only the syndicate creator can assign full_access (admin).

Responses

201Invitation sent
401
403
422Validation error (e.g. already a member)
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/invite \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "name": "string",
    "syndicate_permission": "full_access"
  }'
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
PATCH/syndicates/{syndicate_id}/members/{member_id}

Update member

Update title and carry override for a member. Requires operator permission.

Parameters

string
string

Request Body

memberobjectrequired

Responses

200Member updated
401
403
curl -X PATCH https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
DELETE/syndicates/{syndicate_id}/members/{member_id}

Remove member (requires intent)

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.

Parameters

string
string

Responses

422Use intents for this action
curl -X DELETE https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
POST/syndicates/{syndicate_id}/members/{member_id}/approve

Approve applicant

Approve a pending applicant. Only applicants can be approved via API; invitees must accept their invite link.

Parameters

string
string

Responses

200Member approved
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/approve \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
POST/syndicates/{syndicate_id}/members/{member_id}/hide

Exile member

Exile (hide) a member from the syndicate. Sets their role to exiled.

Parameters

string
string

Responses

200Member hidden
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/hide \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
POST/syndicates/{syndicate_id}/members/{member_id}/promote

Promote member to admin

Promote a member to full_access (admin) role. Only the syndicate creator can promote members.

Parameters

string
string

Responses

200Member promoted
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/promote \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
POST/syndicates/{syndicate_id}/members/{member_id}/demote

Demote member to operator

Demote a member from full_access (admin) to operator permission.

Parameters

string
string

Responses

200Member demoted
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/demote \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
POST/syndicates/{syndicate_id}/members/{member_id}/restore

Restore exiled or resigned member

Restore a member who was previously exiled or resigned. Sets their role back to member.

Parameters

string
string

Responses

200Member restored
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/restore \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "mem_3StSZhDjMHzn8NzEkX0kgEuN",
    "type": "syndicate_member",
    "attributes": {
      "user_id": 123,
      "user": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "user_name": "Jane Smith",
      "user_email": "[email protected]",
      "role": "member",
      "invite_role": "admin",
      "syndicate_permission": "full_access",
      "title": "Example title",
      "sort_order": 5,
      "carry_percentage_override": 20,
      "carry_discount_percentage": 0,
      "management_fee_discount_percentage": 0,
      "public_on_syndicate_profile": true,
      "interests": [
        "string"
      ],
      "stage_preferences": [
        "string"
      ],
      "looking_for": [
        "string"
      ],
      "linkedin_url": "string",
      "twitter_url": "string",
      "website": "string",
      "state": "string",
      "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
      "bio": "Example text",
      "city": "San Francisco",
      "country": "US",
      "last_activity_at": "2025-03-01T12:00:00Z",
      "profile_url": "/janedoe",
      "tags": [
        "Fundraising",
        "Product Strategy"
      ],
      "joined_at": "2025-03-01T12:00:00Z",
      "investment_total": "5000",
      "deal_count": 2,
      "accredited": true,
      "legal_name": "Example Name",
      "starred": true,
      "private_tags": [
        "VIP",
        "Follow up"
      ],
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
POST/syndicates/{syndicate_id}/members/{member_id}/resend_invite

Resend invitation

Parameters

string
string

Responses

200Invitation resent
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/resend_invite \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
POST/syndicates/{syndicate_id}/members/reorder

Reorder members

Set the display order for the member directory.

Parameters

string

Request Body

member_idsarrayrequired

Member ids as returned by the list endpoint (mem_…), in the desired display order. Unknown ids return 404 and change nothing.

Responses

200Order updated
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/reorder \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "member_ids": [
      "mem_8Kd0aB3xQ9k2",
      "mem_vF8mNp1zT5wY",
      "mem_2Lq7cR4sX0bE"
    ]
  }'
POST/syndicates/{syndicate_id}/members/export_csv

Export members CSV

Generate a CSV export of the member directory. Returns the CSV data directly with Content-Type text/csv. Requires operator permission.

Parameters

string

Responses

200CSV data
401
403
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/export_csv \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
POST/syndicates/{syndicate_id}/join_sessionsBeta

Create a join session

Mint a hosted join session that turns an invitee into a member of a syndicate you manage. The session is bound to one email; the recipient completes the join on wefunder.com/join/session/{id}, and you learn the outcome by polling this session or via join_session.* webhooks.

Requires write:syndicate_members (included in write:syndicates), operator permission, and an active installation of your app on the syndicate. Phase 1 supports mode: hosted only.

Parameters

string

Request Body

join_sessionobjectrequired

Responses

201Join session created
401
403
422The request could not be fulfilled: the app is not installed on this
curl -X POST https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/join_sessions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
GET/syndicates/{syndicate_id}/join_sessionsBeta

List join sessions

List the join sessions your app's installation minted on this syndicate. Sessions are install-scoped: you only see the ones your own install created.

Parameters

string
cursorstringquery

Opaque cursor from the previous response's meta.next_cursor.

per_pageintegerquery

Results per page, 1-100. Defaults to 25.

Responses

200Successful response
401
403
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/join_sessions?cursor=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
GET/syndicates/{syndicate_id}/join_sessions/{id}Beta

Get a join session

Fetch one join session your app's installation minted on this syndicate, by its js_ id. Poll this to learn the session's current status.

Parameters

string
idstringrequiredpath

The join session's js_ id.

Responses

200Successful response
401
403
404
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/join_sessions/{id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
GET/syndicates/{syndicate_id}/members/{member_id}/investments

List member investments

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.

Parameters

string
string

Responses

200Successful response
401
403
404Member not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/members/{member_id}/investments \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "inv_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "type": "member_investment",
      "attributes": {
        "fundraise_id": 75496,
        "offering": "ofr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
        "company_name": "Substack",
        "amount": "5000",
        "status": "confirmed",
        "created_at": "2025-03-01T12:00:00Z"
      }
    }
  ],
  "meta": {
    "count": 123,
    "total_amount": "8000"
  }
}

Syndicate Deals

GET/syndicates/{syndicate_id}/deals

List deals

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 the same visible deal set in every state. The visible set is every round of the syndicate's directory companies except invite-only companies' rounds and testing-the-waters rounds; a syndicate with no directory companies falls back to its explicitly linked rounds, excluding invite-only companies' rounds only.

Parameters

string
cursorstringquery

Opaque cursor from the previous response's meta.next_cursor.

per_pageintegerquery

Results per page, 1-100. Defaults to 25.

Responses

200Successful response
401
403
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/deals?cursor=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "ofr_yw3OhusvpfJP4Pk3wRYV1F2R",
      "type": "syndicate_deal",
      "attributes": {
        "name": "Acme Corp Series A",
        "status": "open",
        "company_name": "Acme Corp",
        "company_id": 1234,
        "company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
        "company_url": "acme-corp",
        "company_logo_url": "https://uploads.wefunder.com/uploads/company/logo/1234/large_logo.png",
        "amount_raised": "230000",
        "investor_count": 47,
        "funding_target": "50000000",
        "exemption": null,
        "security": null,
        "min_investment": "100",
        "max_investment": "50000",
        "description": "Example text",
        "created_at": "2025-03-01T12:00:00Z",
        "updated_at": "2025-03-01T12:00:00Z",
        "closed_at": "2025-03-01T12:00:00Z",
        "funding_started_at": "2025-03-01T12:00:00Z"
      }
    }
  ],
  "meta": {
    "page_count": 25,
    "has_more": true,
    "next_cursor": 12345
  }
}
GET/syndicates/{syndicate_id}/deals/{fundraise_id}

Get deal details

Returns full details for a specific deal including investor breakdown.

Parameters

string
string

Responses

200Successful response
401
403
404Deal not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/deals/{fundraise_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "ofr_yw3OhusvpfJP4Pk3wRYV1F2R",
    "type": "syndicate_deal",
    "attributes": {
      "name": "Acme Corp Series A",
      "status": "open",
      "company_name": "Acme Corp",
      "company_id": 1234,
      "company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
      "company_url": "acme-corp",
      "company_logo_url": "https://uploads.wefunder.com/uploads/company/logo/1234/large_logo.png",
      "amount_raised": "230000",
      "investor_count": 47,
      "funding_target": "50000000",
      "exemption": null,
      "security": null,
      "min_investment": "100",
      "max_investment": "50000",
      "description": "Example text",
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z",
      "closed_at": "2025-03-01T12:00:00Z",
      "funding_started_at": "2025-03-01T12:00:00Z"
    }
  }
}
GET/syndicates/{syndicate_id}/deals/{fundraise_id}/investors

List deal investors

Returns syndicate members who invested in this deal. Results are scoped to syndicate member user IDs only and sorted by amount descending.

Manager/operator only. Who invested how much is moderator data, as on the web deal pipeline page: an ordinary member, backer, or follower of the syndicate gets 403, even with read:syndicates. Callers who pass that gate see user_email.

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.

Parameters

string
string
offsetintegerquery

Number of investors to skip.

per_pageintegerquery

Results per page, 1-200. Omit to return every investor.

Responses

200Successful response
401
403The caller is not a manager or operator of the syndicate (an ordinary member,
404Deal not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/deals/{fundraise_id}/investors?offset=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "usr_aB3xQ9k2vF8mNp1zT5wY7Qc4",
      "type": "deal_investor",
      "attributes": {
        "user_id": 456,
        "user": "usr_aB3xQ9k2vF8mNp1zT5wY7Qc4",
        "user_name": "Jane Smith",
        "user_email": "[email protected]",
        "avatar_url": "https://uploads.wefunder.com/uploads/user/avatar/456/large_photo.jpg",
        "amount": "5000",
        "status": "confirmed",
        "invested_at": "2025-03-01T12:00:00Z"
      }
    }
  ],
  "meta": {
    "count": 123,
    "total_count": 123,
    "offset": 123,
    "per_page": 123,
    "has_more": true
  }
}

Syndicate Statistics

GET/syndicates/{syndicate_id}/statistics

Get syndicate statistics

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 directory company: its live round, else its most recent successful round). total_deals counts the syndicate's visible deal set in every state (the rounds GET /syndicates/{syndicate_id}/deals draws from, which excludes invite-only companies' rounds and testing-the-waters rounds of directory companies); live_deals is the live subset of it, the rows that endpoint lists.

total_raised is whole dollars as a string.

Parameters

string

Responses

200Successful response
401
403
404Syndicate not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/statistics \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "type": "syndicate_statistics",
    "attributes": {
      "total_members": 45,
      "members_by_role": {
        "manager": 3,
        "member": 35,
        "invitee": 5,
        "creator": 1,
        "applicant": 1
      },
      "total_deals": 3,
      "live_deals": 1,
      "total_raised": "107800",
      "total_investors": 6527,
      "recent_activity_count": 4
    }
  }
}

Syndicate Portfolio

GET/syndicates/{syndicate_id}/portfolio

Get syndicate portfolio summary

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.

Parameters

string
statusstringquery

Total only deals with this status.

companystringquery

Total only positions in this company, by its id (co_...). Unknown ids return 404.

Responses

200Successful response
401
403
404Syndicate not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/portfolio?status=value&company=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "type": "syndicate_portfolio_summary",
    "attributes": {
      "currency": "usd",
      "other_currencies": [],
      "total_cost_basis_cents": 123,
      "total_current_value_cents": 123,
      "total_unrealized_gain_cents": 123,
      "total_realized_gain_cents": 123,
      "return_multiple": "string",
      "deal_counts": {
        "active": 123,
        "exited": 123,
        "sold": 123,
        "failed": 123
      },
      "investor_count": 123,
      "as_of": "string"
    }
  }
}
GET/syndicates/{syndicate_id}/portfolio/positions

List syndicate portfolio positions

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.

Parameters

string
string
per_pageintegerquery

Page size, 1-100. Defaults to 25.

statusstringquery

Filter positions by status.

companystringquery

Filter to a single company by its id (co_...). Unknown ids return 404.

Responses

200Successful response
400Invalid filter value (unknown status or malformed cursor)
401
403
404Syndicate or filtered company not found
curl https://api.wefunder.com/api/v2/syndicates/{syndicate_id}/portfolio/positions?per_page=value&status=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "ofr_9aKxQ2vF8mNp1zT5wY7Qb3Cd",
      "type": "portfolio_position",
      "attributes": {
        "offering_id": "ofr_9aKxQ2vF8mNp1zT5wY7Qb3Cd",
        "company": {
          "id": "co_2bt9eJn6X8C9gnShl63qlzkK",
          "name": "Acme Robotics",
          "slug": "acme-robotics",
          "url": "https://wefunder.com/acme-robotics"
        },
        "asset_type": "company",
        "security": null,
        "status": "active",
        "exit_reason": "ipo",
        "exited_on": "2026-08-15",
        "invested_at": "string",
        "currency": "usd",
        "cost_basis_cents": 123,
        "current_value_cents": 123,
        "unrealized_gain_cents": 123,
        "realized_gain_cents": 123,
        "return_multiple": "1.6492",
        "shares_held": "string",
        "current_share_price": "string",
        "securities": [
          {
            "security": null,
            "early_bird": true,
            "terms": {
              "share_price": "10.0",
              "currency": "usd",
              "note_cap_cents": 123,
              "uncapped": false,
              "note_discount": "20.0",
              "premoney_valuation_cents": 123
            },
            "cost_basis_cents": 123,
            "current_value_cents": 123,
            "unrealized_gain_cents": 123,
            "realized_gain_cents": 123,
            "shares_held": "string",
            "current_share_price": "string",
            "invested_at": "string",
            "owner": {
              "kind": "individual",
              "name": "string"
            },
            "investor_count": 123
          }
        ],
        "holdings": [
          {
            "company": {
              "id": "co_2bt9eJn6X8C9gnShl63qlzkK",
              "name": "Acme Robotics",
              "slug": "acme-robotics",
              "url": "https://wefunder.com/acme-robotics"
            },
            "status": "active"
          }
        ],
        "investor_count": 123,
        "as_of": "string"
      }
    }
  ],
  "meta": {
    "has_more": true,
    "page_count": 123,
    "next_cursor": 123
  }
}

Intents

POST/intents/preview

Preview an intent without proposing it

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 on the token, so a user can shape the text before granting the write permission. The caller must still be allowed to propose on the resource, and for a syndicate-owned token the application's Installation scope ceiling still applies (an install granted only read:syndicates cannot preview a write:syndicates action: 403 insufficient_scope). Same request body as POST /intents (without idempotency_key). Errors are the same as POST /intents would give.

Request Body

action_namestringrequired
resource_typestringrequired
resource_idstringrequired
paramsobject

Responses

200The proposal would be accepted.
401
403
422The handler refused it; `error.message` is the reason.
curl -X POST https://api.wefunder.com/api/v2/intents/preview \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action_name": "string",
    "resource_type": "string",
    "resource_id": "string"
  }'
Response200 OK
{
  "data": {
    "type": "intent_preview",
    "attributes": {
      "action": "string",
      "resource_type": "string",
      "resource_id": "string",
      "params": null,
      "impact_summary": "string",
      "scope": "string"
    }
  }
}
POST/intents

Propose an intent

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.

syndicates.close_deal and syndicates.finalize_deal were removed in 2026-10-15: on this version they answer 400 bad_request ("Unknown action") with details.removed_in_version. A syndicate's managers close and finalize deals on wefunder.com. Intents minted before still appear in GET /intents with those action names.

See Intents documentation for the full pattern.

Request Body

action_namestringrequired

The action to perform

resource_typestringrequired

The resource type the action targets, as the action table names it; the example is the syndicate type.

resource_idstringrequired

The resource's id in the API's vocabulary — the syndicate's syn_... for a syndicates.* action.

paramsobject

Action-specific parameters

idempotency_keystring

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.

requested_by_agentstring

Human-readable name of the agent proposing the action

Responses

200Idempotent replay — the existing intent for this key and operation.
201Intent created
401
403
409`idempotency_conflict` — the key was already used for a different operation. Nothing was minted.
422Invalid action or parameters
curl -X POST https://api.wefunder.com/api/v2/intents \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action_name": "syndicates.remove_member",
    "resource_type": "Club",
    "resource_id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
    "params": {
      "member_id": "mem_7Hk2mP9qR4sT1uV6wX8yZ3aB"
    },
    "idempotency_key": "remove-mem_7Hk2mP9qR4sT1uV6wX8yZ3aB-2026-10",
    "requested_by_agent": "Claude via MCP"
  }'
Response200 OK
{
  "data": {
    "id": "int_aB3xQ9k2vF8mNp1zT5wY7Qc4",
    "type": "intent",
    "attributes": {
      "action": "syndicates.remove_member",
      "status": "pending",
      "resource_type": "Club",
      "resource_id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
      "impact_summary": "Remove Jane Smith from Acme Syndicate. She will lose access to the syndicate's deals.",
      "review_url": "https://wefunder.com/intents/a1b2c3d4-e5f6-7890-abcd-ef1234567890/review",
      "requested_by_agent": "Claude via MCP",
      "expires_at": "2025-03-01T12:00:00Z",
      "approved_at": "2025-03-01T12:00:00Z",
      "executed_at": "2025-03-01T12:00:00Z",
      "rejection_reason": "Example text",
      "execution_result": null,
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}
GET/intents

List intents

List intents created by the current token, optionally filtered by status or resource.

Parameters

statusstringquery
resource_typestringquery
resource_idstringquery

For Club resources, the syndicate's id (syn_...).

cursorstringquery

Opaque cursor from the previous response's meta.next_cursor.

per_pageintegerquery

Responses

200Successful response
401
curl https://api.wefunder.com/api/v2/intents?status=value&resource_type=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "int_aB3xQ9k2vF8mNp1zT5wY7Qc4",
      "type": "intent",
      "attributes": {
        "action": "syndicates.remove_member",
        "status": "pending",
        "resource_type": "Club",
        "resource_id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
        "impact_summary": "Remove Jane Smith from Acme Syndicate. She will lose access to the syndicate's deals.",
        "review_url": "https://wefunder.com/intents/a1b2c3d4-e5f6-7890-abcd-ef1234567890/review",
        "requested_by_agent": "Claude via MCP",
        "expires_at": "2025-03-01T12:00:00Z",
        "approved_at": "2025-03-01T12:00:00Z",
        "executed_at": "2025-03-01T12:00:00Z",
        "rejection_reason": "Example text",
        "execution_result": null,
        "created_at": "2025-03-01T12:00:00Z",
        "updated_at": "2025-03-01T12:00:00Z"
      }
    }
  ],
  "meta": {
    "page_count": 25,
    "has_more": true,
    "next_cursor": 12345
  }
}
GET/intents/{intent_id}

Get intent status

Check the current status of an intent. Use this to poll for approval after proposing an intent, or subscribe to intent.* webhook events instead.

Parameters

intent_idstringrequiredpath

The intent's id (int_...), as returned when the intent was minted (data.id / meta.*_intent.id). The legacy UUID form is still accepted.

Responses

200Successful response
401
404Intent not found
curl https://api.wefunder.com/api/v2/intents/int_aB3xQ9k2vF8mNp1zT5wY7Qc4 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "int_aB3xQ9k2vF8mNp1zT5wY7Qc4",
    "type": "intent",
    "attributes": {
      "action": "syndicates.remove_member",
      "status": "pending",
      "resource_type": "Club",
      "resource_id": "syn_aB3xQ9k2vF8mNp1zT5wY7Qc4",
      "impact_summary": "Remove Jane Smith from Acme Syndicate. She will lose access to the syndicate's deals.",
      "review_url": "https://wefunder.com/intents/a1b2c3d4-e5f6-7890-abcd-ef1234567890/review",
      "requested_by_agent": "Claude via MCP",
      "expires_at": "2025-03-01T12:00:00Z",
      "approved_at": "2025-03-01T12:00:00Z",
      "executed_at": "2025-03-01T12:00:00Z",
      "rejection_reason": "Example text",
      "execution_result": null,
      "created_at": "2025-03-01T12:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  }
}

Activity

GET/connected-apps

List connected apps

Returns all OAuth apps the user has authorized, with summary stats.

Responses

200Successful response
401
curl https://api.wefunder.com/api/v2/connected-apps \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": 123,
      "type": "connected_app",
      "attributes": {
        "name": "ChatGPT via Wefunder MCP",
        "scopes": [
          "read:syndicates",
          "write:syndicates"
        ],
        "last_active_at": "2025-03-01T12:00:00Z",
        "action_count": 34,
        "created_at": "2025-03-01T12:00:00Z"
      }
    }
  ]
}
DELETE/connected-apps/{app_id}

Revoke connected app

Revoke all tokens for this app. Any pending intents from this app are automatically expired.

Parameters

app_idintegerrequiredpath

Responses

200App revoked, all tokens and pending intents expired
401
404App not found
curl -X DELETE https://api.wefunder.com/api/v2/connected-apps/{app_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "revoked": true,
    "tokens_revoked": 2
  }
}

Referral Codes

GET/referral_codesBeta

List referral codes

The referral codes your application created, newest first. A referral code records that a visitor arrived through your link (?wfr=ref_…); it never changes what the visitor sees and grants no access. Filter with active=true or active=false.

Every referral code operation takes your application's own server key (a client_credentials token). A user or installation token gets 403 app_credentials_required.

Parameters

activebooleanquery
string
per_pageintegerquery

Responses

200Referral codes for the calling application
401
403
curl https://api.wefunder.com/api/v2/referral_codes?active=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "ref_8f3ExampleReferral0000",
      "type": "referral_code",
      "attributes": {
        "label": "October newsletter",
        "active": true,
        "target": {
          "type": "company",
          "id": "co_7hQExampleCompany0001",
          "unavailable": true
        },
        "utm": {
          "source": "acme_newsletter",
          "medium": "email",
          "campaign": "october",
          "content": "header_link",
          "term": "seed_round"
        },
        "metadata": null,
        "link_parameter": "wfr=ref_8f3ExampleReferral0000",
        "stats": null,
        "created_at": "2026-10-13T17:30:00Z",
        "updated_at": "2026-10-13T17:30:00Z"
      }
    }
  ],
  "meta": {
    "has_more": false,
    "page_count": 1,
    "next_cursor": 123,
    "active_limit": 500,
    "request_id": "string"
  }
}
POST/referral_codesBeta

Create a referral code

Mints a referral code for your application. No relationship with the company is needed. Put it on any wefunder.com link as ?wfr=<id>.

Before its first code, your organization must accept the referral attribution terms once: Wefunder does not pay partners per referral, and a partner may not be compensated per referral unless it is a registered broker-dealer. Until an owner or admin accepts them in the developer portal, this returns 422 attribution_terms_required with details.acceptance_url.

An application may have at most 500 active codes (422 quota_exceeded); deactivate codes you no longer use. UTM presets fill blank utm_* fields on visits that carry the code. A source or campaign starting with wf_ is reserved for Wefunder's own channels and returns 422 reserved_utm_prefix. Idempotency keys are not supported here: a retried request may create a second code, which you can deactivate.

Request Body

labelstringrequired

Your name for the code, e.g. a campaign or newsletter issue.

company_idstring

Credit only investments in this company (co_…). Omit both company_id and offering_id for a code that credits any company. Fixed at creation.

offering_idstring

Credit only investments in this offering's company (ofr_…). The offering must be publicly visible. Fixed at creation.

utmobject
metadataobject

Free-form metadata for your own use, at most 4 KB.

Responses

201Referral code created
401
403
404
422`attribution_terms_required` (with `details.acceptance_url`), `quota_exceeded`,
curl -X POST https://api.wefunder.com/api/v2/referral_codes \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "October newsletter",
    "company_id": "co_7hQExampleCompany0001",
    "offering_id": "ofr_7hQExampleOffering01"
  }'
Response200 OK
{
  "data": {
    "id": "ref_8f3ExampleReferral0000",
    "type": "referral_code",
    "attributes": {
      "label": "October newsletter",
      "active": true,
      "target": {
        "type": "company",
        "id": "co_7hQExampleCompany0001",
        "unavailable": true
      },
      "utm": {
        "source": "acme_newsletter",
        "medium": "email",
        "campaign": "october",
        "content": "header_link",
        "term": "seed_round"
      },
      "metadata": null,
      "link_parameter": "wfr=ref_8f3ExampleReferral0000",
      "stats": null,
      "created_at": "2026-10-13T17:30:00Z",
      "updated_at": "2026-10-13T17:30:00Z"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
GET/referral_codes/{id}Beta

Retrieve a referral code

Responses

200The referral code
401
403
404
curl https://api.wefunder.com/api/v2/referral_codes/{id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "ref_8f3ExampleReferral0000",
    "type": "referral_code",
    "attributes": {
      "label": "October newsletter",
      "active": true,
      "target": {
        "type": "company",
        "id": "co_7hQExampleCompany0001",
        "unavailable": true
      },
      "utm": {
        "source": "acme_newsletter",
        "medium": "email",
        "campaign": "october",
        "content": "header_link",
        "term": "seed_round"
      },
      "metadata": null,
      "link_parameter": "wfr=ref_8f3ExampleReferral0000",
      "stats": null,
      "created_at": "2026-10-13T17:30:00Z",
      "updated_at": "2026-10-13T17:30:00Z"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
PATCH/referral_codes/{id}Beta

Update a referral code

Changes the label, pauses or resumes the code (active), or replaces its UTM presets. The target is fixed at creation. A paused code is ignored on arrival; existing cookies still credit investments for up to 30 days. Reactivating counts against the 500 active codes.

Request Body

labelstring
activeboolean
utmobject

Responses

200The updated referral code
401
403
404
422`quota_exceeded`, `reserved_utm_prefix`, or `validation_error`.
curl -X PATCH https://api.wefunder.com/api/v2/referral_codes/{id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "string",
    "active": true
  }'
Response200 OK
{
  "data": {
    "id": "ref_8f3ExampleReferral0000",
    "type": "referral_code",
    "attributes": {
      "label": "October newsletter",
      "active": true,
      "target": {
        "type": "company",
        "id": "co_7hQExampleCompany0001",
        "unavailable": true
      },
      "utm": {
        "source": "acme_newsletter",
        "medium": "email",
        "campaign": "october",
        "content": "header_link",
        "term": "seed_round"
      },
      "metadata": null,
      "link_parameter": "wfr=ref_8f3ExampleReferral0000",
      "stats": null,
      "created_at": "2026-10-13T17:30:00Z",
      "updated_at": "2026-10-13T17:30:00Z"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
GET/referral_codes/{id}/statsBeta

Referral code stats

What your code produced, per company. Your access to each company decides what you see:

  • Counts (any app): for each company in the code's scope, one cell per ISO week (Monday to Sunday, UTC) with the unique investors referred that week as a range, {min, max}. Ranges are never exact, currently of width 3 and starting at zero, so {0, 2} also means none. Each week is published once, early on the following Monday, and never revised; the current week has no cell. There are no totals across weeks or companies, and no amounts.
  • Rows (your app is installed on the company with read:attribution:anonymized, or at founder tier with read:attribution:full): that company's referred investments instead of ranges, anonymized or in full.

A targeted code's scope is its target; an untargeted code's scope is every company its visitors landed on. visits and unique_visitors are a daily snapshot up to the end of visits_as_of (UTC). Paginated over companies; each company's weekly series is complete.

Parameters

string
company_idstringquery

Only this company (co_…). Use it with investments_cursor to page an installed company's rows.

investments_cursorintegerquery

With company_id, the investments_next_cursor from the previous page of that company's rows.

per_pageintegerquery

Responses

200Stats for the referral code
401
403
404
curl https://api.wefunder.com/api/v2/referral_codes/{id}/stats?company_id=value&investments_cursor=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "ref_8f3ExampleReferral0000",
    "type": "referral_code_stats",
    "attributes": null
  },
  "meta": {
    "has_more": false,
    "page_count": 1,
    "next_cursor": 123,
    "request_id": "string"
  }
}

Installations

GET/installations

List installations of your app

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.

Responses

200Installations of the calling application
401
403
curl https://api.wefunder.com/api/v2/installations \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "string",
      "type": "installation",
      "attributes": {
        "target": {
          "type": "company",
          "id": "string",
          "name": "string"
        },
        "tier": "string",
        "scopes": [
          "string"
        ],
        "status": "active",
        "installed_at": "string",
        "installed_by": "string",
        "revoked_at": "string"
      }
    }
  ],
  "meta": {
    "count": 123
  }
}
POST/installations

Install your app on a company or syndicate

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.

Request Body

target_typestringrequired
target_idstringrequired

The company (co_…) or syndicate (syn_…) external id.

scopesarray

Subset of the app's declared scopes to grant. Defaults to all of them.

tierstring

Optional higher tier (founder / full_access), honored only when the caller holds it.

Responses

201The new installation and its token (shown once)
401
403
404
409Already installed here; `error.details.installation` is the existing install's id.
422Unknown `target_type`, or none of the requested scopes can be granted by the caller's role.
curl -X POST https://api.wefunder.com/api/v2/installations \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "target_type": "company",
    "target_id": "string",
    "scopes": [],
    "tier": "string"
  }'
Response200 OK
{
  "data": {
    "id": "string",
    "type": "installation",
    "attributes": {
      "target": {
        "type": "company",
        "id": "string",
        "name": "string"
      },
      "tier": "string",
      "scopes": [
        "string"
      ],
      "status": "active",
      "installed_at": "string",
      "installed_by": "string",
      "revoked_at": "string"
    }
  },
  "token": {
    "access_token": "at_live_…",
    "token_type": "Bearer",
    "scope": "string",
    "installation": "string",
    "created_at": 123
  },
  "meta": null
}
GET/installations/eligible

List where you can install your app

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.

Parameters

target_typestringquery

Responses

200Eligible install targets
401
403
curl https://api.wefunder.com/api/v2/installations/eligible?target_type=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "type": "company",
      "id": "string",
      "name": "string",
      "tier": "string",
      "installed": true,
      "installation": null
    }
  ],
  "meta": null
}
GET/installations/{external_id}

Retrieve an installation

Responses

200The installation
401
404
curl https://api.wefunder.com/api/v2/installations/{external_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "installation",
    "attributes": {
      "target": {
        "type": "company",
        "id": "string",
        "name": "string"
      },
      "tier": "string",
      "scopes": [
        "string"
      ],
      "status": "active",
      "installed_at": "string",
      "installed_by": "string",
      "revoked_at": "string"
    }
  },
  "meta": null
}
DELETE/installations/{external_id}

Revoke an installation

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.

Responses

200The installation, now revoked
401
403
404
curl -X DELETE https://api.wefunder.com/api/v2/installations/{external_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "installation",
    "attributes": {
      "target": {
        "type": "company",
        "id": "string",
        "name": "string"
      },
      "tier": "string",
      "scopes": [
        "string"
      ],
      "status": "active",
      "installed_at": "string",
      "installed_by": "string",
      "revoked_at": "string"
    }
  },
  "meta": null
}
POST/installations/{external_id}/tokens

Mint a token for an installation

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.

Parameters

external_idstringrequiredpath

Request Body

scopesarray

Responses

201The installation and a new token (shown once)
401
403
404
409The installation has been revoked.
curl -X POST https://api.wefunder.com/api/v2/installations/{external_id}/tokens \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scopes": []
  }'
Response200 OK
{
  "data": {
    "id": "string",
    "type": "installation",
    "attributes": {
      "target": {
        "type": "company",
        "id": "string",
        "name": "string"
      },
      "tier": "string",
      "scopes": [
        "string"
      ],
      "status": "active",
      "installed_at": "string",
      "installed_by": "string",
      "revoked_at": "string"
    }
  },
  "token": {
    "access_token": "at_live_…",
    "token_type": "Bearer",
    "scope": "string",
    "installation": "string",
    "created_at": 123
  },
  "meta": null
}

Webhook Endpoints

GET/webhook_endpoints

List webhook endpoints

Your application's webhook endpoints. The signing secret is never included. Requires an owner, admin, or developer role in the application's organization.

Responses

200Endpoints for the calling application
401
403
curl https://api.wefunder.com/api/v2/webhook_endpoints \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "whe_8f3ExampleEndpoint00",
      "type": "webhook_endpoint",
      "attributes": {
        "url": "string",
        "url_hint": "hooks.zapier.com …3kq9Zz",
        "installation": "inst_7hQExampleInstall01",
        "mode": "live",
        "events": [
          "string"
        ],
        "enabled": true,
        "secret": "whsec_4Zb7ExampleSigningSecret0000000000",
        "failing_since": "string",
        "last_delivery_at": "string",
        "last_delivery_status": "200",
        "created_at": "string"
      }
    }
  ],
  "meta": {
    "count": 123,
    "quota": 123,
    "request_id": "string"
  }
}
POST/webhook_endpoints

Create a webhook endpoint

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.

Request Body

urlstringrequired

Public HTTPS URL to receive deliveries (private/internal IPs rejected).

installationstring

Bind the endpoint to one active installation of your app (inst_…) instead of creating an app-wide endpoint. An unknown or revoked id is 404. An installation token may omit this (it always binds to its own installation); a user token may bind only to an installation its holder has a role on at or above the installation's granted tier. Bound endpoints are live-only, capped at 5 per installation, idempotent on (installation, url, mode), and their url is returned only in this response.

eventsarrayrequired

Event names to subscribe to (from the event catalog). At least one.

modestring

Responses

201Endpoint created (includes the one-time secret)
401
403
422Validation error (bad URL, unknown or missing events, invalid mode, or quota reached)
curl -X POST https://api.wefunder.com/api/v2/webhook_endpoints \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/wefunder",
    "installation": "inst_7hQExampleInstall01",
    "events": [
      "offering.opened",
      "investment.executed"
    ],
    "mode": "live"
  }'
Response200 OK
{
  "data": {
    "id": "whe_8f3ExampleEndpoint00",
    "type": "webhook_endpoint",
    "attributes": {
      "url": "string",
      "url_hint": "hooks.zapier.com …3kq9Zz",
      "installation": "inst_7hQExampleInstall01",
      "mode": "live",
      "events": [
        "string"
      ],
      "enabled": true,
      "secret": "whsec_4Zb7ExampleSigningSecret0000000000",
      "failing_since": "string",
      "last_delivery_at": "string",
      "last_delivery_status": "200",
      "created_at": "string"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
GET/webhook_endpoints/{external_id}

Get a webhook endpoint

Requires an owner, admin, or developer role in the application's organization.

Parameters

string

Responses

200The endpoint (secret omitted)
401
403
404
curl https://api.wefunder.com/api/v2/webhook_endpoints/{external_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "whe_8f3ExampleEndpoint00",
    "type": "webhook_endpoint",
    "attributes": {
      "url": "string",
      "url_hint": "hooks.zapier.com …3kq9Zz",
      "installation": "inst_7hQExampleInstall01",
      "mode": "live",
      "events": [
        "string"
      ],
      "enabled": true,
      "secret": "whsec_4Zb7ExampleSigningSecret0000000000",
      "failing_since": "string",
      "last_delivery_at": "string",
      "last_delivery_status": "200",
      "created_at": "string"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
PATCH/webhook_endpoints/{external_id}

Update a webhook endpoint

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.

Parameters

string

Request Body

urlstring
eventsarray

Responses

200Updated endpoint (secret omitted)
401
403
404
422Validation error
curl -X PATCH https://api.wefunder.com/api/v2/webhook_endpoints/{external_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "string",
    "events": []
  }'
Response200 OK
{
  "data": {
    "id": "whe_8f3ExampleEndpoint00",
    "type": "webhook_endpoint",
    "attributes": {
      "url": "string",
      "url_hint": "hooks.zapier.com …3kq9Zz",
      "installation": "inst_7hQExampleInstall01",
      "mode": "live",
      "events": [
        "string"
      ],
      "enabled": true,
      "secret": "whsec_4Zb7ExampleSigningSecret0000000000",
      "failing_since": "string",
      "last_delivery_at": "string",
      "last_delivery_status": "200",
      "created_at": "string"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
DELETE/webhook_endpoints/{external_id}

Remove a webhook endpoint

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.

Parameters

string

Responses

200Removed
401
403
404
curl -X DELETE https://api.wefunder.com/api/v2/webhook_endpoints/{external_id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "string",
    "type": "webhook_endpoint",
    "removed": true
  }
}
POST/webhook_endpoints/{external_id}/rotate_secret

Rotate the signing secret

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.

Parameters

string

Responses

200Rotated (includes the new one-time secret)
401
403
404
curl -X POST https://api.wefunder.com/api/v2/webhook_endpoints/{external_id}/rotate_secret \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "whe_8f3ExampleEndpoint00",
    "type": "webhook_endpoint",
    "attributes": {
      "url": "string",
      "url_hint": "hooks.zapier.com …3kq9Zz",
      "installation": "inst_7hQExampleInstall01",
      "mode": "live",
      "events": [
        "string"
      ],
      "enabled": true,
      "secret": "whsec_4Zb7ExampleSigningSecret0000000000",
      "failing_since": "string",
      "last_delivery_at": "string",
      "last_delivery_status": "200",
      "created_at": "string"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
POST/webhook_endpoints/{external_id}/reenable

Re-enable a disabled endpoint

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.

Parameters

string

Responses

200The (now enabled) endpoint
401
403
404
curl -X POST https://api.wefunder.com/api/v2/webhook_endpoints/{external_id}/reenable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "whe_8f3ExampleEndpoint00",
    "type": "webhook_endpoint",
    "attributes": {
      "url": "string",
      "url_hint": "hooks.zapier.com …3kq9Zz",
      "installation": "inst_7hQExampleInstall01",
      "mode": "live",
      "events": [
        "string"
      ],
      "enabled": true,
      "secret": "whsec_4Zb7ExampleSigningSecret0000000000",
      "failing_since": "string",
      "last_delivery_at": "string",
      "last_delivery_status": "200",
      "created_at": "string"
    }
  },
  "meta": {
    "request_id": "string"
  }
}
POST/webhook_endpoints/{external_id}/test

Send a test event

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.

Parameters

string

Request Body

eventstring

Event to simulate. Defaults to the endpoint's first subscribed event.

Responses

200Test outcome
401
403
404
422Endpoint is not subscribed to the event, or the event has no example
429
curl -X POST https://api.wefunder.com/api/v2/webhook_endpoints/{external_id}/test \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "investment.created"
  }'
Response200 OK
{
  "data": {
    "type": "webhook_test",
    "delivered": true,
    "response_code": 123,
    "error": "timeout",
    "duration_ms": 123,
    "event": "string",
    "payload": null
  },
  "meta": {
    "request_id": "string"
  }
}

Member Links

Partner Invites & Sessions

GET/offerings/{id}/investment_sessionsBeta

List investment sessions

Lists the investment sessions your app created on this offering, newest first; filter by status. A session belongs to the application that created it: no other app sees it, even one of your organization. A syndicate- or company-owned token sees only its own syndicate's or company's sessions. An offering with none of your app's sessions lists nothing, so the id reveals nothing about the offering.

Parameters

string
statusstringquery

Only sessions in this state

per_pageintegerquery

Page size (default 25, max 100)

string

Responses

200Successful response
401
403
422
curl https://api.wefunder.com/api/v2/offerings/{id}/investment_sessions?status=value&per_page=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "is_abc123",
      "type": "investment_session",
      "attributes": {
        "status": "pending",
        "url": "https://wefunder.com/invest/session/is_abc123",
        "spv_id": "spv_abc123",
        "email": "[email protected]",
        "allocation_cents": 5000000,
        "kyc_status": "pending",
        "funding_status": "pending",
        "investment_id": "invt_def789",
        "metadata": null,
        "created_at": "2025-01-15T10:00:00Z",
        "expires_at": "string",
        "events": [
          {
            "id": "evt_abc123",
            "event": "investment_session.completed",
            "occurred_at": "string"
          }
        ]
      }
    }
  ],
  "meta": {
    "has_more": true,
    "page_count": 123,
    "request_id": "string",
    "next_cursor": 123
  }
}
POST/offerings/{id}/investment_sessionsBeta

Create an investment session

Canonical path: the offering comes from the URL, so spv_id may be omitted from the body (if sent it must match, else 422 offering_mismatch). Authorized for any offering the caller may act on (an install on its syndicate or company, a role on it, or created by your app).

Creates a single-use, recipient-specific hosted investment session (analogous to a Stripe Checkout Session) on the SPV named by spv_id in the body. The session walks one investor through accreditation, document signing, and payment, and produces a durable Investment on success. Returns a url the recipient opens to begin. If the recipient already has an active session on the SPV, that session is returned instead of creating a duplicate; if another app holds that recipient's active session there, the create is refused with 409 session_conflict (a recipient has one active session per offering).

Parameters

string
string

Request Body

investment_sessionobjectrequired

Responses

201Investment session created
401
403
404
409
422
curl -X POST https://api.wefunder.com/api/v2/offerings/{id}/investment_sessions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Response200 OK
{
  "data": {
    "id": "is_abc123",
    "type": "investment_session",
    "attributes": {
      "status": "pending",
      "url": "https://wefunder.com/invest/session/is_abc123",
      "spv_id": "spv_abc123",
      "email": "[email protected]",
      "allocation_cents": 5000000,
      "kyc_status": "pending",
      "funding_status": "pending",
      "investment_id": "invt_def789",
      "metadata": null,
      "created_at": "2025-01-15T10:00:00Z",
      "expires_at": "string",
      "events": [
        {
          "id": "evt_abc123",
          "event": "investment_session.completed",
          "occurred_at": "string"
        }
      ]
    }
  },
  "meta": {
    "request_id": "string"
  }
}
GET/investment_sessionsBeta

List investment sessions

Alias of /partner/investment_sessions (#23636).

Lists the investment sessions your app created, newest first. A session belongs to the application that created it: no other app sees it, even one of your organization. A syndicate- or company-owned token sees only its own syndicate's or company's sessions. Creating a session needs authority on the offering; reading or canceling one you created does not. Filter by spv_id and/or status.

Parameters

spv_idstringquery

Only sessions on this SPV

statusstringquery

Only sessions in this state

per_pageintegerquery

Page size (default 25, max 100)

string

Responses

200Successful response
401
403
422
curl https://api.wefunder.com/api/v2/investment_sessions?spv_id=value&status=value \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": [
    {
      "id": "is_abc123",
      "type": "investment_session",
      "attributes": {
        "status": "pending",
        "url": "https://wefunder.com/invest/session/is_abc123",
        "spv_id": "spv_abc123",
        "email": "[email protected]",
        "allocation_cents": 5000000,
        "kyc_status": "pending",
        "funding_status": "pending",
        "investment_id": "invt_def789",
        "metadata": null,
        "created_at": "2025-01-15T10:00:00Z",
        "expires_at": "string",
        "events": [
          {
            "id": "evt_abc123",
            "event": "investment_session.completed",
            "occurred_at": "string"
          }
        ]
      }
    }
  ],
  "meta": {
    "has_more": true,
    "page_count": 123,
    "request_id": "string",
    "next_cursor": 123
  }
}
GET/investment_sessions/{id}Beta

Get an investment session

Alias of /partner/investment_sessions (#23636).

Returns the full session including an events timeline of durable platform events recorded about it.

Parameters

idstringrequiredpath

The investment session ID (prefixed, e.g. is_abc123)

Responses

200Successful response
401
403
404
curl https://api.wefunder.com/api/v2/investment_sessions/is_abc123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "is_abc123",
    "type": "investment_session",
    "attributes": {
      "status": "pending",
      "url": "https://wefunder.com/invest/session/is_abc123",
      "spv_id": "spv_abc123",
      "email": "[email protected]",
      "allocation_cents": 5000000,
      "kyc_status": "pending",
      "funding_status": "pending",
      "investment_id": "invt_def789",
      "metadata": null,
      "created_at": "2025-01-15T10:00:00Z",
      "expires_at": "string",
      "events": [
        {
          "id": "evt_abc123",
          "event": "investment_session.completed",
          "occurred_at": "string"
        }
      ]
    }
  },
  "meta": {
    "request_id": "string"
  }
}
DELETE/investment_sessions/{id}Beta

Cancel an investment session

Alias of /partner/investment_sessions (#23636).

Cancels a session so its link stops working. Valid while the session is pending, in_progress, or abandoned; repeating the call on an already-canceled session returns the same 200. A completed or expired session cannot be canceled (422 session_not_cancelable).

Parameters

idstringrequiredpath

The investment session ID (prefixed, e.g. is_abc123)

Responses

200Session canceled (state `canceled` in the response)
401
403
404
422
curl -X DELETE https://api.wefunder.com/api/v2/investment_sessions/is_abc123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response200 OK
{
  "data": {
    "id": "is_abc123",
    "type": "investment_session",
    "attributes": {
      "status": "pending",
      "url": "https://wefunder.com/invest/session/is_abc123",
      "spv_id": "spv_abc123",
      "email": "[email protected]",
      "allocation_cents": 5000000,
      "kyc_status": "pending",
      "funding_status": "pending",
      "investment_id": "invt_def789",
      "metadata": null,
      "created_at": "2025-01-15T10:00:00Z",
      "expires_at": "string",
      "events": [
        {
          "id": "evt_abc123",
          "event": "investment_session.completed",
          "occurred_at": "string"
        }
      ]
    }
  },
  "meta": {
    "request_id": "string"
  }
}