putty

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 --version

Put that line in your shell profile so it survives a new terminal.

2. Install

pnpm install

One 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:up

That brings up two containers:

ServiceHost portWhat the API reads it as
Postgres55432DATABASE_URL
Dragonfly6379DRAGONFLY_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:ps

4. 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.local

For a working local run, two things matter:

  • DATABASE_URL in apps/api/.env.local must point at port 55432, and DRAGONFLY_URL at 6379, to match the compose file.
  • NEXT_PUBLIC_PRIVY_APP_ID in apps/website/.env.local is 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:seed

The 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 dev

That starts every app that defines a dev script, in parallel, and streams all of their output. You now have:

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 dev

6. Make your first call

Check the process is up:

curl -s http://localhost:3001/health

GET /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 -40

The OpenAPI document

Every route the server registers is described in one document:

curl -s http://localhost:3001/openapi.json | head -5

The 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 -40

That 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:

ProjectResult
apps/api1,934 passed, 23 skipped (110 files)
packages/contracts264 passed, 0 failed (10 Foundry suites)
packages/catalog48 passed (7 files)
packages/chains34 passed (2 files)
packages/templates45 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 build

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

On this page