# API Reference — Locus Build

> Companion guide to [`SKILL.md`](./SKILL.md). Complete endpoint table.

## When To Load

Load this file only when you need a specific endpoint, request shape, or response field quickly.

**Base URL:** `https://api.buildwithlocus.com/v1`
**MPP Base URL:** `https://mpp.buildwithlocus.com/v1`

## Table of Contents

- [All Endpoints](#all-endpoints)

## All Endpoints

| Action | Endpoint | Notes |
|--------|----------|-------|
| **Exchange API key** | `POST /v1/auth/exchange` | Body: `{"apiKey":"claw_..."}` → `{token, expiresIn}` |
| **Refresh token** | `POST /v1/auth/refresh` | No body; use existing Bearer token |
| **Whoami** | `GET /v1/auth/whoami` | Returns `{userId, workspaceId, email}` |
| **MPP sign-up** | `POST https://mpp.buildwithlocus.com/v1/auth/mpp-sign-up` | `{tempoAddress?}` — wallet-based workspace bootstrap via Tempo MPP payment ($0.001 USDC). Returns `{jwt, workspaceId, isNewWorkspace, claimUrl?}` |
| **x402 sign-up** | `POST /v1/auth/x402-sign-up` | Wallet-based workspace bootstrap via x402 USDC (Polygon or Base). Returns `{jwt, workspaceId, isNewWorkspace, claimUrl?}`. Initial credit: $6.00 |
| **Get claim link** | `GET /v1/auth/claim-link` | Returns `{hasClaim, claimUrl, token, expiresAt}` — outstanding claim link for workspace |
| **Claim status** | `GET /v1/auth/claim-status/:token` | Public. Returns `{status}` — `valid`, `used`, `expired`, or `not_found` |
| **Redeem claim** | `POST /v1/auth/claim-redeem` | Public. `{token, email}` — links email to workspace, sends login invite |
| **Create project** | `POST /v1/projects` | `{name, description?, region?}` — `region`: `us-east-1` (default) or `sa-east-1` |
| **List projects** | `GET /v1/projects` | `?limit=50&offset=0` → `{projects:[...], total}` (max 100) |
| **Get project** | `GET /v1/projects/:projectId` | |
| **Update project** | `PATCH /v1/projects/:projectId` | `{name?, description?}` → returns updated project object |
| **Delete project** | `DELETE /v1/projects/:projectId` | |
| **Create environment** | `POST /v1/projects/:projectId/environments` | `{name, type: development\|staging\|production}` |
| **List environments** | `GET /v1/projects/:projectId/environments` | |
| **Get environment** | `GET /v1/projects/:projectId/environments/:envId` | |
| **Set env variables** | `PUT /v1/projects/:projectId/environments/:envId/variables` | Replaces all shared vars |
| **Delete environment** | `DELETE /v1/projects/:projectId/environments/:envId` | 409 if environment has services |
| **Verify locusbuild** | `POST /v1/projects/verify-locusbuild` | `{locusbuild}` — validates structure, returns `{valid, errors, plan}`. Always call before deploying |
| **From repo (monorepo)** | `POST /v1/projects/from-repo` | `{name, repo, branch?, region?}` — reads `.locusbuild`, creates project + env + services + addons + deploys. `region` from `.locusbuild` or request body (`us-east-1` default, `sa-east-1` available) |
| **From locusbuild** | `POST /v1/projects/from-locusbuild` | `{name, repo, branch?, locusbuild}` — like `from-repo` but `.locusbuild` passed in body. `repo` must be a real GitHub repo (source is cloned from it). `.locusbuild` may include top-level `region` field |
| **Create service** | `POST /v1/services` | `{projectId, environmentId, name, source, runtime?, buildConfig?, startCommand?, healthCheckPath?, errorPatterns?}` — costs $0.25/month, returns 402 if insufficient credits. `source.imageUri` required when `type=image`, `source.repo` required when `type=github`. Service name must be unique per environment (409 if duplicate). `buildConfig: {method?, dockerfile?, buildArgs?}` — buildArgs only applied on fresh builds, not redeploy |
| **Get service** | `GET /v1/services/:serviceId` | `?include=runtime` adds `runtime_instances: {runningCount, desiredCount, pendingCount}` (or `{..., status: "not_deployed"}` if not yet deployed). Response includes `deploymentStatus`, `lastDeploymentId`, `lastDeployedAt` |
| **Update service** | `PATCH /v1/services/:serviceId` | `{name?, autoDeploy?, runtime?, startCommand?, healthCheckPath?, errorPatterns?}` — returns updated service object |
| **Delete service** | `DELETE /v1/services/:serviceId` | |
| **List services** | `GET /v1/services/environment/:environmentId` | `?include=runtime` adds `runtime_instances: {runningCount, desiredCount, pendingCount}` per service. Response includes `deploymentStatus`, `lastDeploymentId`, `lastDeployedAt` |
| **Restart service** | `POST /v1/services/:serviceId/restart` | Rolling restart without rebuild |
| **Redeploy service** | `POST /v1/services/:serviceId/redeploy` | Redeploys latest successful image (skips build) or re-runs source build |
| **Set variables** | `PUT /v1/variables/service/:serviceId` | Replaces all service vars |
| **Merge variables** | `PATCH /v1/variables/service/:serviceId` | Adds/updates without removing |
| **Resolved variables** | `GET /v1/variables/service/:serviceId/resolved` | Includes addon-injected vars |
| **Variable dependencies** | `GET /v1/variables/service/:serviceId/dependencies` | Dependency graph |
| **Trigger deployment** | `POST /v1/deployments` | `{serviceId, source?}` → response includes auto-assigned `version` (monotonically increasing per service, starting at 1) |
| **Get deployment** | `GET /v1/deployments/:deploymentId` | Status polling. Includes `version`, `durationMs` (null if not complete). Failed deployments include `lastLogs[]` (last 20 log lines) |
| **List deployments** | `GET /v1/deployments/service/:serviceId` | `?status=&limit=` |
| **List all deployments** | `GET /v1/deployments/recent` | `?limit=` — recent deployments across workspace |
| **Cancel deployment** | `POST /v1/deployments/:deploymentId/cancel` | Cancels queued/building/deploying. Returns 409 if terminal |
| **Rollback deployment** | `POST /v1/deployments/:deploymentId/rollback` | `{reason?}` — deploys previous healthy image |
| **Stream logs** | `GET /v1/deployments/:deploymentId/logs` | `?follow=true` for SSE; phase-aware (build vs runtime). Non-streaming returns `{logs, phase, reason, deploymentStatus}` |
| **Search deploy logs** | `GET /v1/deployments/:deploymentId/logs/search` | `?pattern=ERROR&since=1h&limit=100` — `limit` accepts number or numeric string |
| **Service logs** | `GET /v1/services/:serviceId/logs` | `?follow=true` for SSE; runtime logs for a running service |
| **Search service logs** | `GET /v1/services/:serviceId/logs/search` | `?pattern=ERROR&since=1h&limit=100` |
| **Create addon** | `POST /v1/addons` | `{projectId, environmentId, type: postgres\|redis, name?, config?}` — costs $0.25/month, returns 402 if insufficient credits. Response includes `note` about redeploying |
| **Get addon** | `GET /v1/addons/:addonId` | Poll until `available`. Response includes `requiresRedeploy` boolean |
| **List addons** | `GET /v1/addons/environment/:envId` | `?type=postgres\|redis` |
| **Delete addon** | `DELETE /v1/addons/:addonId` | Deprovisions infrastructure |
| **List tables** | `GET /v1/addons/:addonId/data/tables` | Postgres: table names, row counts, sizes |
| **Describe table** | `GET /v1/addons/:addonId/data/tables/:table` | Column names, types, PKs |
| **Query rows** | `GET /v1/addons/:addonId/data/tables/:table/rows` | `?limit=&offset=&orderBy=&orderDir=` |
| **Table stats** | `GET /v1/addons/:addonId/data/stats` | Vacuum/analyze stats |
| **Read-only SQL** | `POST /v1/addons/:addonId/data/query` | `{sql, params?}` — SELECT only |
| **List Redis keys** | `GET /v1/addons/:addonId/data/keys` | `?pattern=&cursor=&count=` |
| **Get Redis key** | `GET /v1/addons/:addonId/data/keys/:key` | Value + type + TTL |
| **Redis memory** | `GET /v1/addons/:addonId/data/memory` | Memory usage + key count |
| **Execute SQL** | `POST /v1/addons/:addonId/execute/sql` | `{sql, params?}` — allows writes |
| **Execute Redis** | `POST /v1/addons/:addonId/execute/redis` | `{command, args?}` — filtered |
| **Run migration** | `POST /v1/addons/:addonId/execute/migrate` | `{sql}` — transactional, auto-rollback |
| **List all domains** | `GET /v1/domains` | All domains in workspace. Purchased domains include `registrationPrice` with `renewalPrice` |
| **Register domain (BYOD)** | `POST /v1/domains` | `{domain, projectId?}` → cert + CNAME target |
| **Verify domain** | `POST /v1/domains/:domainId/verify` | Checks CNAME + cert status |
| **Check availability** | `GET /v1/domains/check-availability?domain=` | Returns availability + price |
| **Get suggestions** | `GET /v1/domains/suggestions?keywords=&tlds=` | Domain name suggestions |
| **Purchase domain** | `POST /v1/domains/purchase` | `{domain, contact, projectId?}` → 202 |
| **Registration status** | `GET /v1/domains/:domainId/registration-status` | Poll until `registered` |
| **List domains by project** | `GET /v1/domains/project/:projectId` | Filters workspace domains by project |
| **Get domain** | `GET /v1/domains/:domainId` | |
| **Check cert status** | `GET /v1/domains/:domainId/status` | |
| **Auto-create DNS** | `POST /v1/domains/:domainId/create-dns-records` | Locus-managed DNS only |
| **Attach domain** | `POST /v1/domains/:domainId/attach` | `{serviceId}` |
| **Detach domain** | `POST /v1/domains/:domainId/detach` | |
| **Delete domain** | `DELETE /v1/domains/:domainId` | Must detach first |
| **Git push deploy** | `POST /v1/git/push-deploy` | Called by git server; triggers all services in project |
| **Get git remote URL** | `GET /v1/git/remote-url` | Returns `{remoteUrl, usage}` — workspace base URL + format template. Agent must add projectId + API key |
| **Check repo access** | `GET /v1/github/repo-access?repo=` | Returns accessibility + install URL if needed |
| **GitHub install URL** | `GET /v1/github/install-url` | Returns `{installUrl}` for the GitHub App |
| **List GitHub installs** | `GET /v1/github/installations` | Returns workspace's GitHub App installations |
| **Save GitHub install** | `POST /v1/github/installations` | `{installationId: number}` — after GitHub redirect |
| **Remove GitHub install** | `DELETE /v1/github/installations/:id` | Removes installation mapping |
| **Create webhook** | `POST /v1/webhooks` | `{projectId, url, events, description?}` — auto-generates secret |
| **List webhooks** | `GET /v1/webhooks` | `?projectId=` optional filter |
| **Get webhook** | `GET /v1/webhooks/:webhookId` | |
| **Update webhook** | `PATCH /v1/webhooks/:webhookId` | `{url?, events?, active?, description?}` |
| **Delete webhook** | `DELETE /v1/webhooks/:webhookId` | |
| **Test webhook** | `POST /v1/webhooks/:webhookId/test` | Sends a test event |
| **Webhook deliveries** | `GET /v1/webhooks/:webhookId/deliveries` | `?limit=20` — delivery logs with status codes, response bodies (7-day retention) |
| **Create bug report** | `POST /v1/bug-reports` | `{title, description, serviceId?, deploymentId?, severity?, metadata?}` |
| **Dashboard metrics** | `GET /v1/metrics/dashboard` | Workspace metrics summary |
| **Service usage** | `GET /v1/metrics/services/:serviceId/usage` | CPU/memory metrics |
| **Service errors** | `GET /v1/metrics/services/:serviceId/errors` | `?pattern=` override. Uses `service.errorPatterns` if set, otherwise default. Returns `filterPattern` |
| **Cost estimate** | `GET /v1/metrics/cost-estimate` | Cost breakdown |
| **Usage summary** | `GET /v1/usage/summary` | `?month=YYYY-MM` — monthly usage + costs |
| **Usage events** | `GET /v1/usage/events` | Detailed usage events |
| **Pricing rates** | `GET /v1/usage/pricing` | Current pricing rates |
| **Billing balance** | `GET /v1/billing/balance` | Credit balance + billing summary (creditBalance, totalServices, monthlyTotal, nextBillingDate, status) |
| **Add credits** | `POST /v1/billing/add-credits` | `{amount, apiKey}` — proxy to paywithlocus.com. Returns `{status, creditBalance?, paymentId, approvalUrl?}` |
| **Transaction history** | `GET /v1/billing/transactions` | `?limit=50` — credit/debit ledger |
| **MPP top-up** | `POST https://mpp.buildwithlocus.com/v1/billing/mpp-top-up` | `{amount, tempoAddress?}` — credit top-up via Tempo MPP payment. Requires `X-Mpp-Payment` header or `X-Service-Token` |
| **x402 top-up** | `POST /v1/billing/x402-top-up` | `{amount}` — credit top-up via x402 USDC (Polygon or Base). Payment negotiated via x402 protocol headers |
| **x402 fund wallet** | `POST /v1/billing/x402-fund` | `{amount, targetWalletAddress}` — fund an existing workspace by linked wallet address via x402 USDC. Does not return a workspace JWT |
| **Billable services** | `GET /v1/billing/services` | All billable services with rate breakdown |
| **List approvals** | `GET /v1/approvals` | Pending approvals |
| **Get approval** | `GET /v1/approvals/:approvalId` | Approval details |
| **Approve** | `POST /v1/approvals/:approvalId/approve` | Approve pending action |
| **Reject** | `POST /v1/approvals/:approvalId/reject` | Reject pending action |

## Response Conventions

| Status code | Meaning | Used for |
|-------------|---------|----------|
| `200` | OK | GET, PATCH, POST actions |
| `201` | Created | POST that creates a new resource |
| `204` | No Content | DELETE — empty response body |
| `400` | Bad Request | Validation errors |
| `401` | Unauthorized | Bad or expired token |
| `402` | Payment Required | Insufficient credits |
| `404` | Not Found | Resource doesn't exist |
| `409` | Conflict | Action not allowed in current state |
| `500` | Internal Server Error | Server-side failure |

## Error Response Format

All error responses return JSON with an `error` string and optional extra fields:

```json
{"error": "Human-readable message", "details": "...optional extra context..."}
```

Special fields on specific status codes:

| Status | Extra fields | Example |
|--------|-------------|---------|
| `400` | `invalidEvents`, `validEvents` | Webhook creation with unknown event types |
| `402` | `creditBalance`, `requiredAmount` | Service creation when workspace has insufficient credits |
| `409` | `error` describes current state | Cancelling a terminal deployment |

## Query Parameter Types

Numeric query parameters (e.g., `limit`, `offset`, `count`) accept both numbers and numeric strings — `?limit=10` works whether the HTTP client sends it as a number or string.

Boolean query parameters (e.g., `follow`) accept `true`, `false`, `1`, `0` as strings or native booleans. For example, `?follow=true` and `?follow=1` both enable log streaming.
