# Agent Quick Start — Locus Build

> Companion guide to [`SKILL.md`](./SKILL.md). Copy-paste deploy scripts, deployment status streaming, and error recovery cheatsheet.
>
> **First time?** Start with [onboarding.md](./onboarding.md) for auth setup. This quickstart assumes you already have a valid `$TOKEN`.

## Deploy in 3 Steps (GitHub Repo)

Copy-paste this script to go from zero to a deployed service. Replace `YOUR_API_KEY` and `YOUR_GITHUB_REPO`.

```bash
# Step 1: Authenticate and save token
TOKEN=$(curl -s -X POST $BASE_URL/auth/exchange \
  -H "Content-Type: application/json" \
  -d '{"apiKey":"YOUR_API_KEY"}' | jq -r '.token')
echo "$TOKEN" > /tmp/locus-token.txt
echo "Authenticated: $TOKEN"

# Step 2: Create everything in one call (project + env + services + deploy)
RESULT=$(curl -s -X POST $BASE_URL/projects/from-repo \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repo":"YOUR_GITHUB_REPO","branch":"main"}')
PROJECT_ID=$(echo $RESULT | jq -r '.project.id')
DEPLOY_ID=$(echo $RESULT | jq -r '.deployments[0].id // empty')
SERVICE_URL=$(echo $RESULT | jq -r '.services[0].url // empty')
echo "Project: $PROJECT_ID | Deploy: $DEPLOY_ID | URL: $SERVICE_URL"

# Step 3: Poll until healthy (typically 3-7 minutes)
while true; do
  STATUS=$(curl -s -H "Authorization: Bearer $TOKEN" \
    "$BASE_URL/deployments/$DEPLOY_ID" | jq -r '.status')
  echo "$(date +%H:%M:%S) — $STATUS"
  case "$STATUS" in healthy|failed|cancelled|rolled_back) break ;; esac
  sleep 60
done
echo "Service live at: $SERVICE_URL"
```

## Deployment Status Streaming (SSE)

Real-time status updates without polling.

**Endpoint:** `GET /v1/deployments/:deploymentId/status`

| Parameter | Default | Description |
|-----------|---------|-------------|
| `follow` | `false` | `false`: single JSON response. `true`: SSE stream until terminal status. |
| `token` | — | Bearer token (alternative to Authorization header, useful for EventSource) |

### Lightweight Poll (follow=false)

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/deployments/$DEPLOY_ID/status"
```

Response:
```json
{ "deploymentId": "deploy_abc", "status": "building", "updatedAt": "2026-03-13T..." }
```

### SSE Stream (follow=true)

```bash
curl -N -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/deployments/$DEPLOY_ID/status?follow=true"
```

Events:
```
data: {"type":"status","status":"queued","deploymentId":"deploy_abc"}
data: {"type":"status","status":"building","previousStatus":"queued","timestamp":"..."}
data: {"type":"status","status":"deploying","previousStatus":"building","timestamp":"..."}
data: {"type":"status","status":"healthy","previousStatus":"deploying","timestamp":"..."}
data: {"type":"complete","finalStatus":"healthy","durationMs":245000}
```

The stream sends heartbeat comments every 15s to keep connections alive. Closes automatically on terminal status.

### Using with EventSource (browser/Node)

```javascript
const es = new EventSource(
  `${BASE_URL}/deployments/${deployId}/status?follow=true&token=${token}`
);
es.onmessage = (e) => {
  const data = JSON.parse(e.data);
  if (data.type === 'complete') {
    console.log(`Done: ${data.finalStatus} in ${data.durationMs}ms`);
    es.close();
  } else if (data.type === 'status') {
    console.log(`Status: ${data.status}`);
  }
};
```

## Credit Warnings

The API proactively warns when credits are low.

**`GET /v1/billing/balance`** now includes a `warnings` array:

```json
{
  "creditBalance": 0.25,
  "warnings": [
    {
      "level": "warning",
      "message": "Low balance ($0.25). You can create 1 more service(s). Add credits: POST /v1/billing/pay with { \"amount\": <dollars>, \"apiKey\": \"your_key\" }",
      "servicesRemaining": 1
    }
  ]
}
```

**Service creation** (`POST /v1/services`) also returns `warnings` in the 201 response when the post-creation balance is low.

| Balance | Level | Action |
|---------|-------|--------|
| <= $0.25 | `warning` | Imminent — may not afford next service |
| <= $0.50 | `info` | Consider adding funds soon |
| > $0.50 | *(none)* | No warnings |

## Error Recovery Cheatsheet

Every error code from the API maps to an exact fix command.

| Error Code | HTTP | Cause | Fix |
|------------|------|-------|-----|
| `AUTH_MISSING_TOKEN` | 401 | No `Authorization: Bearer` header | Add header: `-H "Authorization: Bearer $TOKEN"` |
| `AUTH_TOKEN_EXPIRED` | 401 | JWT expired | Get fresh token: `TOKEN=$(curl -s -X POST $BASE_URL/auth/exchange -H "Content-Type: application/json" -d '{"apiKey":"YOUR_KEY"}' \| jq -r '.token')` |
| `AUTH_TOKEN_INVALID` | 401 | Token malformed or wrong environment | Re-exchange API key. Check beta vs prod base URL. |
| `AUTH_SERVICE_ERROR` | 401 | Auth service temporarily down | Wait 5 seconds, retry the request |
| *(none)* | 402 | Insufficient credits | `curl -X POST $BASE_URL/billing/pay -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"amount":1,"apiKey":"YOUR_KEY"}'` |
| *(none)* | 404 | Resource not found | Verify resource ID exists: `GET /v1/{resource}/{id}` |
| *(none)* | 503 | Service discovery delay after healthy | Normal — wait 60 seconds after `healthy` status, then retry |

### Quick Recovery Script

```bash
# If you get a 401, run this to refresh and retry:
TOKEN=$(curl -s -X POST $BASE_URL/auth/exchange \
  -H "Content-Type: application/json" \
  -d '{"apiKey":"YOUR_API_KEY"}' | jq -r '.token')
echo "$TOKEN" > /tmp/locus-token.txt

# Verify token works:
curl -s $BASE_URL/auth/whoami -H "Authorization: Bearer $TOKEN"
```

### Decision Tree: Which Error Am I Hitting?

```
Got an error?
    │
    ├── 401? → Check the error code in the response body
    │    ├── AUTH_MISSING_TOKEN → Add Authorization header
    │    ├── AUTH_TOKEN_EXPIRED → Re-exchange API key
    │    ├── AUTH_TOKEN_INVALID → Wrong token or mixed beta/prod URLs
    │    └── AUTH_SERVICE_ERROR → Transient, retry in 5s
    │
    ├── 402? → Need credits
    │    └── POST /v1/billing/pay with amount + apiKey
    │
    ├── 404? → Resource doesn't exist
    │    └── List resources to find correct ID
    │
    ├── 503 after deploy reached healthy? → Service discovery delay
    │    └── Wait 60 seconds, retry
    │
    └── 500? → Server error
         └── File a bug report: POST /v1/bug-reports
```
