Building CLI Tools
A command-line tool is the shortest path from "I have a token" to "I have my Wefunder data in a terminal or a spreadsheet". This page shows the shape of one; the API Reference has every field.
Getting a token
CLI tools act as the person running them, so they use the user-authorized OAuth flow (Authorization Code with PKCE). Run the flow once, store the access and refresh tokens in your OS keychain or an environment variable, and refresh when a request returns 401. See Authentication.
A client_credentials token only reaches the public catalog (read:public), which is fine for a deal-browsing CLI and useless for anything account-specific.
The examples below read the access token from WEFUNDER_ACCESS_TOKEN.
JavaScript
#!/usr/bin/env node
const { program } = require("commander");
const axios = require("axios");
const api = axios.create({
baseURL: "https://api.wefunder.com",
headers: { Authorization: `Bearer ${process.env.WEFUNDER_ACCESS_TOKEN}` }
});
program
.command("syndicates")
.description("List the syndicates you manage")
.action(async () => {
const { data } = await api.get("/syndicates");
console.table(data.data.map((s) => ({
id: s.id,
name: s.attributes.name,
members: s.attributes.member_count,
deals: s.attributes.deal_count
})));
});
program
.command("positions")
.description("List your portfolio positions")
.action(async () => {
const { data } = await api.get("/portfolio/positions", { params: { per_page: 100 } });
console.table(data.data.map((p) => ({ id: p.id, ...p.attributes })));
});
program.parse();
Python
#!/usr/bin/env python3
import os
import click
import httpx
BASE_URL = "https://api.wefunder.com"
HEADERS = {"Authorization": f"Bearer {os.environ['WEFUNDER_ACCESS_TOKEN']}"}
@click.group()
def cli():
pass
@cli.command()
def syndicates():
"""List the syndicates you manage."""
r = httpx.get(f"{BASE_URL}/syndicates", headers=HEADERS)
r.raise_for_status()
for s in r.json()["data"]:
a = s["attributes"]
click.echo(f"{s['id']} {a['name']} members={a['member_count']} deals={a['deal_count']}")
@cli.command()
def positions():
"""List your portfolio positions."""
r = httpx.get(f"{BASE_URL}/portfolio/positions", headers=HEADERS, params={"per_page": 100})
r.raise_for_status()
for p in r.json()["data"]:
click.echo(f"{p['id']} {p['attributes']}")
if __name__ == "__main__":
cli()
Both examples need the matching scopes on the token: read:syndicates for the first command, read:investments for the second.
Practices worth copying
- Keep credentials out of source. Environment variables or the OS keychain; never a committed config file.
- Follow
meta.next_cursoron every list command, or add a--allflag that does. See Pagination. - Retry only
429and5xx, with backoff. See Error Handling. - Print
request_idon failures so a support conversation starts with the right identifier. - Prefer webhooks for change detection if your tool runs as a daemon. See Handling Webhooks.