Skip to main content

Your First API Request

Two requests: one against the public deal catalog with a server key, one on behalf of a Wefunder user. Together they exercise both ways the API is authorized.

Prerequisites​

  • An application in the developer portal, with its client ID (pk_…) and client secret (sk_…). See Authentication.
  • curl and jq, or any HTTP client.

Use the _test_ client to work against your sandbox realm, or the _live_ client to hit real data. The requests below are identical either way.

Step 1: Read the public catalog​

A client-credentials token carries only read:public, and that is all the catalog needs.

TOKEN=$(curl -s -X POST "https://api.wefunder.com/oauth/token" \
-d "grant_type=client_credentials" \
-d "client_id=pk_live_YOUR_CLIENT_ID" \
-d "client_secret=sk_live_YOUR_CLIENT_SECRET" \
-d "scope=read:public" | jq -r .access_token)

curl -s "https://api.wefunder.com/explore?sort=most_raised" \
-H "Authorization: Bearer $TOKEN"

Response (trimmed):

{
"data": [
{
"id": "ofr_yw3OhusvpfJP4Pk3wRYV1F2R",
"type": "offering",
"company": "co_8Kd0aB3xQ9k2vF8mNp1zT5wY",
"attributes": {
"company_name": "ZeroBorder Self-Banking Centers",
"url": "https://wefunder.com/zeroborder",
"status": "open",
"testing_the_waters": false,
"intended_security": { "type": "safe", "label": "SAFE" },
"funding_target": "1000000",
"amount_raised": "642300",
"investor_count": 1203,
"closes_at": "2025-04-30T03:59:59Z"
}
}
],
"meta": {
"total_count": 119,
"page_count": 25,
"has_more": true,
"next_cursor": 25
}
}

Money fields are decimal strings, never floats. testing_the_waters: true marks a round that is taking reservations, not investments.

Step 2: Act on behalf of a user​

Send a user through the Authorization Code flow requesting read:profile read:investments, then call the API with the token they granted.

curl -s "https://api.wefunder.com/users/me" \
-H "Authorization: Bearer at_live_USER_TOKEN"

Response (trimmed; the full field list is on GET /users/me):

{
"data": {
"id": "usr_8Kd0aB3xQ9k2vF8mNp1zT5wY",
"type": "user",
"attributes": {
"username": "john_doe",
"full_name": "John Doe",
"city": "San Francisco",
"country": "USA",
"created_at": "2023-01-15T10:30:00Z"
}
}
}

Users, companies, offerings and investments are identified by prefixed strings (usr_, co_, ofr_, inv_), not numbers. Store them as opaque strings.

Now list the investments that user can see, which covers their own investments plus the investments into any company they edit:

curl -s "https://api.wefunder.com/investments?per_page=2" \
-H "Authorization: Bearer at_live_USER_TOKEN"
{
"data": [
{
"id": "inv_…",
"status": "executed",
"offering": "ofr_…",
"company": "co_…",
"applied_at": "2026-03-02T18:04:11Z",
"amounts": { "committed_cents": 50000, "in_escrow_cents": 0, "currency": "usd" },
"shares": "295",
"average_share_price": "3.38"
}
],
"meta": {
"mode": "bootstrap",
"has_more": true,
"next_cursor": "…",
"published_through": "2026-09-28T23:59:59Z"
}
}

Save next_cursor and pass it back as ?cursor= to page, or later as a sync point. The guide Investments: list, sync, retrieve covers the record shape and syncing.

When something goes wrong​

Every error has the same envelope. request_id is in the body; quote it when you write to support.

{
"error": {
"type": "insufficient_scope",
"message": "This endpoint requires the read:investments scope",
"request_id": "req_err789"
}
}
Statuserror.typeMeaning
400bad_requestMalformed parameter or cursor
401unauthorizedToken missing, invalid, or expired
403insufficient_scope / forbiddenToken lacks the scope, or the user cannot see this resource
404not_foundNo such resource, or not visible to this token
422validation_errorA field failed validation; see details.errors
429rate_limit_exceededBack off; see Rate limiting

More in Error handling.

Next steps​

Was this page helpful?