Getting started
Run the whole stack locally and make your first call, from a fresh checkout to a working API.
This page takes a fresh checkout to a running local API and a first successful call. It assumes you have Node 22 and pnpm 10.33.0 already.
If you are calling the deployed API rather than running it, go to the
Quick start instead — that is about minting an sk- key and
pointing an OpenAI SDK at https://api.putty.wtf/v1.
1. Foundry on your PATH
This comes first because everything else is blocked without it.
@putty/contracts wraps the forge binary in its build, lint, format,
typecheck and test scripts, and those scripts are recursive from the repo
root. Foundry installs to ~/.foundry/bin, and nothing puts it on PATH. If
you skip this, every root command fails with spawn ENOENT before a single
check runs:
export PATH="$PATH:$HOME/.foundry/bin"
forge --versionPut that line in your shell profile so it survives a new terminal.
2. Install
pnpm installOne lockfile at the root covers every app and package. If you add a package, do
not create a pnpm-lock.yaml or a pnpm-workspace.yaml inside it.
3. Start the data services
The apps run on your host; the data services they talk to run in Docker.
pnpm run compose:upThat brings up two containers:
| Service | Host port | What the API reads it as |
|---|---|---|
| Postgres | 55432 | DATABASE_URL |
| Dragonfly | 6379 | DRAGONFLY_URL |
Postgres is on 55432 rather than 5432 so it does not collide with a host-installed Postgres. Check both came up:
pnpm run compose:ps4. Configure
Each app keeps its own env file, and each ships an example that documents every variable inline.
cp apps/api/.env.example apps/api/.env.local
cp apps/website/.env.example apps/website/.env.localFor a working local run, two things matter:
DATABASE_URLinapps/api/.env.localmust point at port 55432, andDRAGONFLY_URLat 6379, to match the compose file.NEXT_PUBLIC_PRIVY_APP_IDinapps/website/.env.localis needed for the dashboard to sign a user in. Without it the marketing pages still build and render; the dashboard's auth will not.
You do not need real credentials to start. The API's strict requireEnv checks
only fire when NODE_ENV is production or prod — in dev it uses the
in-memory store and starts with no database and no Privy configuration at all.
Then apply the schema and seed the free tier:
pnpm --filter api db:push
pnpm --filter api db:seedThe seed looks the free tier up by slug. If it fails with a clear message
about a missing tier, the migration has not been applied — run db:push (or
db:deploy) first.
5. Run it
pnpm run devThat starts every app that defines a dev script, in parallel, and streams all
of their output. You now have:
| App | URL |
|---|---|
| website | http://localhost:3000 |
| api | http://localhost:3001 |
| docs | http://localhost:3002 |
Generated code runs in E2B sandboxes, which is the only sandbox provider.
It is optional for a first run — with no E2B_API_KEY the API logs that the
sandbox provider is unavailable and every other route keeps working, which is
what you want until you are actually running a project's code.
To run one app at a time instead, filter:
pnpm --filter api dev6. Make your first call
Check the process is up:
curl -s http://localhost:3001/healthGET /health performs no dependency checks, so a 200 means the API answered,
not that its downstreams are healthy. That is deliberate — it is a liveness
probe, and it stays cheap enough to hit constantly.
You will get {"status":"ok","uptimeSeconds":N}. Now the service document:
curl -s http://localhost:3001/ | head -40The OpenAPI document
Every route the server registers is described in one document:
curl -s http://localhost:3001/openapi.json | head -5The API Reference in this sidebar
is generated from the committed copy of that file, so a route that is not in
the document does not exist for a client. There is no index page at the bare
/docs/api-reference — the generated pages are one per operation, at
/docs/api-reference/<tag>/<operationId>, and the link above is the
Meta tag's first one. The served document is browsable directly at
http://localhost:3001/openapi.json, and every tag carries a description.
The MCP tool list
curl -s http://localhost:3001/mcp/tools | head -40That returns all 75 tools with their input and output schemas. It is public — no bearer needed — so you can read the whole agent-facing surface without an account.
An authenticated route
Most routes need a bearer. A Privy access token from a signed-in dashboard
session works, and so does an sk- API key:
curl -s http://localhost:3001/agents \
-H "Authorization: Bearer $PUTTY_API_KEY"7. The test suites
pnpm run test is recursive, and five of the workspace projects define a
test script. On a clean tree, measured:
| Project | Result |
|---|---|
apps/api | 1,934 passed, 23 skipped (110 files) |
packages/contracts | 264 passed, 0 failed (10 Foundry suites) |
packages/catalog | 48 passed (7 files) |
packages/chains | 34 passed (2 files) |
packages/templates | 45 passed (4 files) |
The api's 23 skipped tests are the real-upstream suites. They are gated behind
RUN_E2E=1 and run with pnpm --filter api test:e2e, so the default run never
reaches a network. apps/docs, apps/website and packages/putty-design
define no test script and are skipped by the recursive run.
8. Before you commit
The repository's Definition of done is six commands from the repo root, in order. Run all six:
pnpm install
pnpm run format
pnpm run lint
pnpm run typecheck
pnpm run test
pnpm run buildIf format leaves a dirty git diff, something was not formatted — fix that
first. If a command fails, say so and treat it as a blocker; never disable a
rule to make it pass.
AGENTS.md in the repo root is the full contributor contract: the workspace
rules, the API and OpenAPI conventions, and what each check must satisfy.
Troubleshooting
spawn ENOENT on any root command. Foundry is not on PATH. Step 1.
A build or test run kills the machine. The root scripts pass
--workspace-concurrency=4 for this reason. If you invoke a build tool directly
and it fans out across all cores, you are bypassing the cap.
The API throws Missing required environment variable at boot. You are
running with NODE_ENV=production or prod. Every variable in
apps/api/.env.example says whether it is required in production only.
The website's dashboard pages fail to render. NEXT_PUBLIC_PRIVY_APP_ID is
unset. The marketing pages are unaffected.