-
Notifications
You must be signed in to change notification settings - Fork 1
REST API Reference
Complete endpoint catalog as of v0.10.0. All routes are defined as
constants in shared/.../ApiPath.kt; copy them into your client to stay
wire-compatible.
All /api/* routes (except /api/auth/setup, /api/auth/setup/status,
/api/auth/login, and /health) require Bearer authentication:
Authorization: Bearer <token>
Obtain a token with POST /api/auth/login. The same token works with both
the header and the vibe_session cookie path.
http://<host>:17880
For LAN deployments. HTTPS is the operator's responsibility (reverse proxy recommended for non-LAN exposure).
Check whether an admin user exists. Used by clients to decide between login and setup screen.
curl http://localhost:17880/api/auth/setup/status
# → {"adminExists": true}Create the first admin user (only when no admin exists). Returns
409 Conflict if one already exists.
curl -X POST http://localhost:17880/api/auth/setup \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"ChangeMe123","deviceName":"my-laptop"}'
# → {"token":"...","deviceId":"...","serverName":"...","username":"admin"}curl -X POST http://localhost:17880/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"ChangeMe123","deviceName":"my-android"}'
# → {"token":"...","deviceId":"...","serverName":"...","username":"admin"}10 consecutive failures lock the account for 15 minutes (timing-safe).
Change password. Requires Bearer auth.
curl -X POST http://localhost:17880/api/auth/password \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{"currentPassword":"old","newPassword":"new"}'
# → 204 No ContentHigh-level summary.
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:17880/api/server/status{
"serverName": "Vibe Coder Server",
"serverVersion": "0.10.0",
"osName": "Linux 6.x",
"javaVersion": "17.0.19",
"workspaceRoot": "/workspace",
"projectCount": 3,
"runningTaskCount": 0,
"claudeAvailable": true,
"androidSdkAvailable": true,
"gitAvailable": true,
"freeDiskSpaceBytes": 102400000000
}Detailed per-component diagnostics.
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:17880/api/projects
# → [{"id":"my-app","name":"My App","packageName":"...", ...}]Empty project:
curl -X POST http://localhost:17880/api/projects/register \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"projectId": "my-app",
"appName": "My App",
"packageName": "com.siamakerlab.myapp"
}'Clone from git:
curl -X POST http://localhost:17880/api/projects/register \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"projectId": "my-app",
"appName": "My App",
"packageName": "com.siamakerlab.myapp",
"sourceType": "clone",
"cloneUrl": "https://github.com/owner/repo.git",
"cloneBranch": "main"
}'Private SSH URL — first register a PAT or generate an SSH key via the Git integrations endpoints below.
RegisterProjectRequestDto.keystore is accepted by the server but is not
exposed in the admin SSR project-create form — it is reachable only via
this JSON endpoint (curl / mobile client / direct API).
curl -X POST http://localhost:17880/api/projects/register \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"projectId": "my-app",
"appName": "My App",
"packageName": "com.siamakerlab.myapp",
"keystore": {
"alias": "myapp",
"password": "at-least-6-chars",
"dname": "CN=Jangwook Lee, OU=Mobile, O=Sia Makerlab, L=Jecheon, ST=Chungbuk, C=KR",
"validityDays": 36500
}
}'KeystoreRequestDto fields:
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
alias |
String | ✅ | — | Non-blank. Becomes keyAlias in the generated .properties. |
password |
String | ✅ | — | Used for both storePassword and keyPassword. Minimum 6 chars. |
dname |
String? | — | Sia Makerlab default DName |
keytool -dname value. |
validityDays |
Int | — |
36500 (~100 yr) |
keytool -validity. |
Output files (always outside the project folder, in the workspace sidecar):
<workspace>/.vibecoder/keystores/<projectId>/
<projectId>.keystore ← keytool -genkeypair output
<projectId>-keystore.properties ← Gradle-readable signing config
.properties contents:
storeFile=<workspace>/.vibecoder/keystores/<projectId>/<projectId>.keystore
storePassword=<plain-password>
keyAlias=<alias>
keyPassword=<plain-password>Security caveat (current implementation).
KeystoreGeneratorinvokeskeytool -storepass <plain> -keypass <plain>— the password is therefore briefly visible inps -ef//proc/<pid>/cmdlineon the host while the command runs (~1 s). Acceptable under vibe-coder's single-operator LAN threat model, but if you share the host with anyone, generate the keystore manually outside the container and only drop the.keystorefile in. A future release will migrate to-storepass:env. Tracked in roadmap.
If keystore.password policy is stricter than the 6-char minimum, generate
the keystore separately and place the files under
<workspace>/.vibecoder/keystores/<projectId>/ yourself.
To skip keystore generation entirely, omit the field or send "keystore": null
(current default).
Queue a debug build. Returns immediately with a BuildDto; stream live
output via /ws/projects/{id}/builds/{buildId}/logs.
List recent builds.
Send SIGTERM (then SIGKILL after 5 s) to the build subprocess.
Stream the produced APK.
curl -X POST http://localhost:17880/api/projects/my-app/claude/console/prompt \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"text":"Add a settings screen with a dark mode toggle"}'
# → 202 Accepted (response streamed over WebSocket)Tear down the current session and start fresh (--resume <id> is dropped).
Model, plan, quota remaining (cached 60 s).
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:17880/api/env-setup/components{
"components": [
{"id":"java","displayName":"JDK 17","status":"INSTALLED","installable":false, ...},
{"id":"android-sdk","displayName":"Android SDK","status":"MISSING","installable":true, ...},
{"id":"claude-auth","displayName":"Claude 로그인","status":"INSTALLED","installable":false, ...}
]
}Trigger sequential install of every installable component. Returns
{"taskId":"..."}. Subscribe to /ws/env-setup/{taskId}/logs for live
output.
Install a specific component (e.g. android-sdk).
The ComponentStateDto returned by GET /api/env-setup/components has
no installingTaskId (or equivalent) field. Its full shape is:
id, displayName, description, sizeHint, status, message, installable
That means the wire does not tell you which component is currently installing. The contract is:
- Client calls
POST .../install(orinstall-all). - Server returns
EnvSetupTaskDto{ taskId }once. - Client stores the
(componentId → taskId)mapping itself and subscribes to/ws/env-setup/{taskId}/logs. - When
WsFrame.Donearrives, the client drops the mapping and may refreshGET /componentsto confirm the newstatus.
The server has a lastTask[component] cache internally (used by the SSR
template to render "View live progress →" links), but it is intentionally
not exposed in any JSON response — different clients can be installing
different components concurrently from different sessions, so per-client
ownership of the taskId is the right model.
If your UI shows a per-component "installing…" spinner, do it from local
state seeded by the install response, not by re-polling /components.
Upload .credentials.json obtained on another machine.
curl -X POST http://localhost:17880/api/env-setup/claude-auth/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@$HOME/.claude/.credentials.json"
# → {"targetPath":"/home/vibe/.claude/.credentials.json", ...}curl -X POST http://localhost:17880/api/env-setup/claude-auth/api-key \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"apiKey":"sk-ant-..."}'
# → 204 No ContentRemove the API key (also accepts POST for clients that lack DELETE).
Spawn claude auth login wrapped in script -q. Returns the initial
state.
curl -X POST http://localhost:17880/api/env-setup/claude-login/start \
-H "Authorization: Bearer $TOKEN"{
"id":"task-...",
"state":"STARTING",
"url":null,
"startedAt":"2026-05-23T12:00:00Z",
"updatedAt":"2026-05-23T12:00:00Z",
"errorMessage":null,
"lastLines":[]
}Poll /api/env-setup/claude-login/status every 1 s until state becomes
AWAITING_CODE (and url is set).
Same shape as above. Returns 204 if no session active.
curl -X POST http://localhost:17880/api/env-setup/claude-login/submit \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"code":"abc...#xyz"}'State → VERIFYING, then DONE or FAILED.
Destroy the child process. State → CANCELED.
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:17880/api/env-setup/mcp{
"entries": [
{
"id":"github",
"displayName":"GitHub",
"pkg":"@modelcontextprotocol/server-github",
"description":"...",
"category":"Git 호스팅 (GitHub / GitLab / Gitea / Bitbucket)",
"trust":"VERIFIED",
"recommended":true,
"homepage":null,
"configFields":[
{"key":"GITHUB_PERSONAL_ACCESS_TOKEN","label":"GitHub PAT","placeholder":"ghp_...","isSecret":true,"required":true,"help":"..."}
],
"status":"NOT_INSTALLED",
"configValues":{},
"comingSoon":false
},
...
]
}The configFields[] entries have a fixed shape with these 8 keys
(no type, no default — those concepts don't exist in the catalog):
| Key | Type | Required | Default | Purpose |
|---|---|---|---|---|
key |
String | ✅ | — | Stored verbatim as the env var name in .mcp.json (e.g. GITHUB_PERSONAL_ACCESS_TOKEN). |
label |
String | ✅ | — | Human-readable text for the form label. |
placeholder |
String? | — | null |
HTML placeholder attribute, e.g. "ghp_...". |
isSecret |
Boolean | — | false |
Render as <input type="password"> / masked field. |
required |
Boolean | — | true |
Server rejects install with 400 missing_config if a required field is empty. |
help |
String? | — | null |
Tooltip / sub-label describing the field. |
isFile |
Boolean | — | false |
v0.11.0+. true → render a file picker and call the multipart upload endpoint below. |
acceptMime |
String? | — | null |
When isFile=true, value for HTML <input accept="...">, e.g. ".json,application/json". |
Notes:
- A field's value type is always String at the wire level. Files go through the multipart upload endpoint and the server returns an absolute path which the client then sends back as the String value.
- There is no
defaultfield in the wire. If a catalog entry needs a sensible default (e.g.--workspace-root /workspaceforfilesystem), it is baked intoargsTemplateinMcpCatalog.kt, not exposed to the client.
curl -X POST http://localhost:17880/api/env-setup/mcp/install \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"selections": {
"filesystem": {},
"github": {"GITHUB_PERSONAL_ACCESS_TOKEN":"ghp_..."},
"context7": {}
}
}'
# → {"taskId":"..."} (subscribe to /ws/env-setup/{taskId}/logs)Removes entries from .mcp.json. The npm package itself stays on disk.
curl -X POST http://localhost:17880/api/env-setup/mcp/unregister \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"ids":["github","slack"]}'
# → 204 No ContentUpload a secret file (Service Account JSON, Apple .p8, OAuth client.json)
for an MCP entry whose configFields[].isFile is true.
curl -X POST http://localhost:17880/api/env-setup/mcp/google-play-publisher/file/GOOGLE_PLAY_SERVICE_ACCOUNT_JSON \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./play-sa.json"
# → {"path":"/home/vibe/.config/mcp-secrets/google-play-publisher-GOOGLE_PLAY_SERVICE_ACCOUNT_JSON.json"}Then pass the returned path as the value for that config key in the
subsequent POST /api/env-setup/mcp/install:
curl -X POST http://localhost:17880/api/env-setup/mcp/install \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"selections": {
"google-play-publisher": {
"GOOGLE_PLAY_SERVICE_ACCOUNT_JSON": "/home/vibe/.config/mcp-secrets/google-play-publisher-GOOGLE_PLAY_SERVICE_ACCOUNT_JSON.json",
"GOOGLE_PLAY_PACKAGE_NAME": "com.siamakerlab.myapp"
}
}
}'- File saved with 0600 permissions in
${CLAUDE_CONFIG_DIR}/mcp-secrets/. - 128 KB size cap (normal Service Account /
.p8is a few KB). - Filename sanitized — only
[A-Za-z0-9._-]in the stored name. - Re-upload to replace (atomic move).
List registered PATs (masked) + SSH public key.
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:17880/api/settings/git-integrations{
"tokens": [
{"provider":"github","host":"github.com","username":"x-access-token","tokenMasked":"••••••••wxyz","createdAt":"2026-05-23T12:00:00Z","note":null}
],
"sshPublicKey": "ssh-ed25519 AAAA... vibe-coder"
}curl -X POST http://localhost:17880/api/settings/git-integrations \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"provider":"github",
"host":"github.com",
"username":"x-access-token",
"token":"ghp_...",
"note":"vibe-coder PAT, expires 2026-12"
}'
# → 204 No Contentcurl -X POST http://localhost:17880/api/settings/git-integrations/delete \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"host":"github.com"}'Generate ed25519 key pair if missing. Returns the full state including the public key for the user to copy.
{
"code": "missing_clone_url",
"message": "sourceType=clone 일 때 cloneUrl 이 필수입니다.",
"detail": null
}HTTP status codes: 400 bad input, 401 no/invalid token, 403 insufficient
permission, 404 not found, 409 conflict, 413 payload too large, 502
upstream failed (git clone, npm install), 504 timeout.