{"name":"Sycord Deployer API","version":"0.9.5","description":"Connect Sycord websites and external projects to the Syte deployer. project_connect returns a uuid — your application must persist it.","base_url":"/sycord/api","documentation":"/sycord/api/","integration_guide":"/sycord/api/integration.json","authentication":{"type":"api_key","header":"X-API-Key","alternative":"Authorization: Bearer <token>","query_param":"api_key (SSE streams only)","create_token":"POST /api/tokens {\"name\": \"sycord\"} — Syte GUI → Users","example_header":"X-API-Key: syte_xxxxxxxxxxxxxxxx"},"uuid_persistence":{"required":true,"when":"Immediately after POST /sycord/api/project_connect","field":"uuid","also_in":"response.persist.uuid, response.project.uuid","format":"{slugified-name}-{6 hex chars} e.g. testproject-a1b2c3","custom_uuid":"Optional body.uuid on project_connect if you need a fixed id","instruction":"Store the returned uuid in your project record before any other Sycord API call. Re-connecting the same name creates a new uuid unless you pass body.uuid.","example_database":{"table":"sycord_projects","columns":{"id":"your internal id","name":"user-facing project name","syte_uuid":"from project_connect response.uuid — REQUIRED","syte_domain":"from response.project.domain","syte_url":"from response.project.url"}}},"stacks":["nextjs","python","javascript","html5"],"workflow":["1. POST /sycord/api/project_connect {name, stack} → save response.uuid","2. POST /sycord/api/upload {uuid, path, file} — add or update files","3. POST /sycord/api/preview_start {uuid} — fast dev preview with HMR (~5s)","4. GET /sycord/api/preview_status?uuid= — poll until preview_ready=true","5. POST /sycord/api/issue_deployment {uuid} — docker build + deploy","6. GET /sycord/api/container_get?uuid= — poll until running=true","7. POST /sycord/api/domain {uuid, domain} — optional custom hostname"],"errors":{"401_missing_api_key":"Send X-API-Key or Authorization: Bearer","401_invalid_api_key":"Token revoked or incorrect","400_invalid_stack":"stack must be one of: nextjs, python, javascript, html5","400_connect_failed":"Duplicate subdomain or validation error","400_upload_failed":"Bad path or project not found","400_preview_failed":"No dev script in package.json or preview process failed","404_not_found":"uuid does not exist"},"backend_integration":{"title":"Sycord backend integration guide","audience":"Developers building sycord.site or any app that talks to Syte deployer","content_type":"application/json for all POST bodies; multipart for upload","prerequisites":{"syte_api_key":"Create in Syte GUI → Users → Create token, or POST /api/tokens","env_var":"SYTE_API_KEY=syte_xxxxxxxx (server-side only, never expose to browser)","dns":"Wildcard *.sycord.site → Syte server IP (for auto subdomains)","preview_dns":"Optional: set preview_base_domain in Syte Settings for a separate preview zone (wildcard *.{zone})"},"your_database":{"description":"Minimum columns to add to your projects table","schema_example":{"id":"uuid — your internal primary key","user_id":"who owns the project","name":"display name from your UI","stack":"nextjs | python | javascript | html5","syte_uuid":"STRING — from Syte project_connect response.uuid (REQUIRED)","syte_domain":"STRING — e.g. myapp.sycord.site","syte_url":"STRING — https://myapp.sycord.site","deploy_status":"created | deploying | running | stopped — mirror Syte status"}},"steps":[{"step":1,"name":"Connect project to Syte","endpoint":"POST https://your-syte-host.com/sycord/api/project_connect","when_to_call":"User creates a new project in your Sycord app (or first time linking to deployer).","request":{"method":"POST","headers":{"Content-Type":"application/json","X-API-Key":"syte_YOUR_TOKEN"},"body_json":{"name":"myapp","stack":"nextjs","uuid":null,"env_vars":{}},"body_fields":{"name":{"type":"string","required":true,"description":"Project name; becomes subdomain slug"},"stack":{"type":"string","required":false,"default":"nextjs","enum":["nextjs","python","javascript","html5"]},"uuid":{"type":"string","required":false,"description":"Your own Syte id if you need a fixed mapping"},"env_vars":{"type":"object","required":false,"description":"Extra env vars for container"}}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","message":"Empty project myapp-a1b2c3 created. Scaffolded…","persist":{"save_uuid":true,"uuid":"myapp-a1b2c3"},"project":{"uuid":"myapp-a1b2c3","name":"myapp","domain":"myapp.sycord.site","url":"https://myapp.sycord.site","stack":"nextjs","status":"created","port":3010}},"fields_to_save":{"uuid":{"required":true,"your_column":"syte_uuid","description":"Primary link to Syte — use in every future API call"},"project.domain":{"required":false,"your_column":"syte_domain","description":"Auto-assigned hostname"},"project.url":{"required":false,"your_column":"syte_url","description":"HTTPS URL to show user once deployed"},"project.status":{"required":false,"your_column":"deploy_status","description":"Usually 'created' until first deploy"}}},"backend_pseudocode":"async function onUserCreatesProject(name, stack) {\n  const res = await fetch(SYTE_URL + '/sycord/api/project_connect', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-API-Key': SYTE_API_KEY },\n    body: JSON.stringify({ name, stack }),\n  });\n  const data = await res.json();\n  if (!data.ok) throw new Error(data.detail?.message);\n  await db.insert({ name, syte_uuid: data.uuid, syte_domain: data.project.domain,\n    syte_url: data.project.url, deploy_status: data.project.status });\n  return { internalId: row.id, syteUuid: data.uuid, url: data.project.url };\n}"},{"step":2,"name":"Upload files (optional)","endpoint":"POST https://your-syte-host.com/sycord/api/upload","when_to_call":"After connect, if you need to push files beyond the scaffold (custom code, assets). Skip if scaffold is enough.","request":{"method":"POST","headers":{"X-API-Key":"syte_YOUR_TOKEN"},"content_type":"multipart/form-data","fields":{"uuid":{"type":"string","required":true,"source":"your syte_uuid column"},"path":{"type":"string","required":true,"example":"app/app/page.tsx","note":"Relative to Syte workspace root"},"file":{"type":"binary","required":true}}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","path":"app/app/page.tsx","bytes":2048,"message":"Uploaded 2048 bytes to app/app/page.tsx"},"fields_to_use":{"ok":"true if upload succeeded","bytes":"optional — log or show in UI"}},"backend_pseudocode":"const form = new FormData();\nform.append('uuid', project.syte_uuid);\nform.append('path', 'app/app/page.tsx');\nform.append('file', fileBlob);\nawait fetch(SYTE_URL + '/sycord/api/upload', { method: 'POST', headers: { 'X-API-Key': KEY }, body: form });"},{"step":3,"name":"Start dev preview (fast HMR)","endpoint":"POST https://your-syte-host.com/sycord/api/preview_start","when_to_call":"After connect (and optional upload), when user wants a live dev preview without a full Docker deploy — typically ~5 seconds for nextjs/vite.","request":{"method":"POST","headers":{"Content-Type":"application/json","X-API-Key":"syte_YOUR_TOKEN"},"body_json":{"uuid":"myapp-a1b2c3"},"body_fields":{"uuid":{"type":"string","required":true,"source":"your syte_uuid column"}}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","message":"Preview on https://previewk-myapp.sycord.site — ready (HMR live)","preview_url":"https://previewk-myapp.sycord.site","preview_domain":"previewk-myapp.sycord.site","preview_ready":true,"preview_running":true,"preview_port":4001,"preview_status":"running","preview_stream_url":"/api/projects/myapp-a1b2c3/preview/logs/stream?live=1"},"fields_to_use":{"preview_url":"Show to user — HTTPS preview link (wildcard *.sycord.site)","preview_ready":"true when dev server is accepting connections","preview_running":"true while preview process is alive","preview_stream_url":"Optional — append to SYTE_URL for live preview logs (SSE)"},"iframe_embedding":{"note":"Embed preview_url in an iframe on sycord.com (or any site when preview_embed_mode=any). Caddy strips X-Frame-Options, COOP, COEP, HSTS and sets frame-ancestors CSP.","example_html":"<iframe src=\"{preview_url}\" sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\" style=\"width:100%;height:100%;border:0\" referrerpolicy=\"no-referrer-when-downgrade\"></iframe>","avoid":"Do not use sandbox without allow-scripts — page will stay blank","setting":"preview_embed_mode=restricted limits frame-ancestors to sycord.com + GUI domain only","debug_endpoint":"GET /api/projects/{uuid}/preview/iframe-check — hoster checklist with live header probe","hoster_checklist":["No X-Frame-Options on preview responses","CSP frame-ancestors includes https://sycord.com and https://*.sycord.com","No COOP same-origin / COEP require-corp from dev server (stripped by Caddy)","No HSTS on preview subdomain","HTTPS on :443 via Caddy (not raw :4000 in iframe src)","Wildcard DNS *.zone → server; valid TLS cert for preview subdomain","Preview dev server running and returning HTML (not empty)"]}},"backend_pseudocode":"const res = await postJson('/sycord/api/preview_start', { uuid: project.syte_uuid });\nstartPollingPreview(project.syte_uuid, res.preview_url);"},{"step":4,"name":"Poll preview status","endpoint":"GET https://your-syte-host.com/sycord/api/preview_status?uuid=<syte_uuid>","when_to_call":"Every 1–2 seconds after preview_start until preview_ready=true.","request":{"method":"GET","headers":{"X-API-Key":"syte_YOUR_TOKEN"},"query":{"uuid":"myapp-a1b2c3"}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","preview_url":"https://previewk-myapp.sycord.site","preview_ready":true,"preview_running":true,"preview_status":"running"},"fields_to_use":{"preview_ready":"true → embed or link preview_url for user","preview_status":"starting | running | stopped"}},"backend_pseudocode":"const st = await getJson('/sycord/api/preview_status?uuid=' + syte_uuid);\nif (st.preview_ready) showPreview(st.preview_url);\nelse if (st.preview_running) { /* keep polling */ }"},{"step":5,"name":"Start deployment","endpoint":"POST https://your-syte-host.com/sycord/api/issue_deployment","when_to_call":"User clicks Deploy in your app, or after file uploads are complete.","request":{"method":"POST","headers":{"Content-Type":"application/json","X-API-Key":"syte_YOUR_TOKEN"},"body_json":{"uuid":"myapp-a1b2c3"},"body_fields":{"uuid":{"type":"string","required":true,"source":"your syte_uuid column"}}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","message":"Deploy issued for myapp-a1b2c3. Stream logs: GET /api/projects/…/logs/stream","stream_url":"/api/projects/myapp-a1b2c3/logs/stream?live=1","status":"deploying"},"fields_to_use":{"status":"Set deploy_status='deploying' in your DB","stream_url":"Optional — append to SYTE_URL for live build logs (SSE)"}},"backend_pseudocode":"const res = await postJson('/sycord/api/issue_deployment', { uuid: project.syte_uuid });\nawait db.update(project.id, { deploy_status: res.status });\nstartPollingContainer(project.syte_uuid);"},{"step":6,"name":"Poll container status","endpoint":"GET https://your-syte-host.com/sycord/api/container_get?uuid=<syte_uuid>","when_to_call":"Every 3–5 seconds after issue_deployment until running=true or failed.","request":{"method":"GET","headers":{"X-API-Key":"syte_YOUR_TOKEN"},"query":{"uuid":"myapp-a1b2c3"}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","container_name":"syte-myapp-a1b2c3","exists":true,"running":true,"state":"running","url":"https://myapp.sycord.site","domain":"myapp.sycord.site","host_port":3010,"status":"running"},"fields_to_use":{"running":"true → site is live; show url to user","url":"Update syte_url; use as href in your dashboard","status":"Mirror to deploy_status (deploying | running | stopped)","state":"Docker state string for debugging"}},"backend_pseudocode":"const st = await getJson('/sycord/api/container_get?uuid=' + syte_uuid);\nif (st.running) {\n  await db.update(id, { deploy_status: 'running', syte_url: st.url });\n  notifyUser('Live at ' + st.url);\n} else if (st.status === 'deploying') { /* keep polling */ }"},{"step":7,"name":"Custom domain (optional)","endpoint":"POST https://your-syte-host.com/sycord/api/domain","when_to_call":"User configures a custom hostname instead of the auto subdomain.","request":{"method":"POST","headers":{"Content-Type":"application/json","X-API-Key":"syte_YOUR_TOKEN"},"body_json":{"uuid":"myapp-a1b2c3","domain":"shop.example.com"}},"response":{"content_type":"application/json","example":{"ok":true,"uuid":"myapp-a1b2c3","message":"Domain set to shop.example.com…","project":{"uuid":"myapp-a1b2c3","domain":"shop.example.com","url":"https://shop.example.com"}},"fields_to_use":{"project.domain":"Update syte_domain","project.url":"Update syte_url shown in UI"}}}],"quick_reference":[{"when":"User creates a project in your app","call":"POST https://your-syte-host.com/sycord/api/project_connect","you_send":"{\"name\":\"…\",\"stack\":\"nextjs\"}","you_save":"response.uuid → syte_uuid column","you_show_user":"response.project.url as preview/deploy link"},{"when":"User uploads or you push generated files","call":"POST https://your-syte-host.com/sycord/api/upload","you_send":"multipart: uuid, path, file","you_save":"nothing (optional log bytes)","you_show_user":"upload success toast"},{"when":"User wants live dev preview (fast, HMR)","call":"POST https://your-syte-host.com/sycord/api/preview_start","you_send":"{\"uuid\":\"<syte_uuid>\"}","you_save":"preview_url from response (optional cache)","you_show_user":"preview_url in iframe or new tab; poll preview_status"},{"when":"Polling preview (every 1–2s)","call":"GET https://your-syte-host.com/sycord/api/preview_status?uuid=<syte_uuid>","you_send":"query param uuid only","you_save":"preview_url when preview_ready=true","you_show_user":"Open preview_url when ready"},{"when":"User clicks Deploy","call":"POST https://your-syte-host.com/sycord/api/issue_deployment","you_send":"{\"uuid\":\"<syte_uuid>\"}","you_save":"deploy_status=deploying","you_show_user":"deploying spinner; poll container_get"},{"when":"Polling after deploy (every 3–5s)","call":"GET https://your-syte-host.com/sycord/api/container_get?uuid=<syte_uuid>","you_send":"query param uuid only","you_save":"deploy_status from response.status; syte_url from response.url","you_show_user":"Open response.url when running=true"},{"when":"User sets custom domain","call":"POST https://your-syte-host.com/sycord/api/domain","you_send":"{\"uuid\":\"…\",\"domain\":\"custom.sycord.site\"}","you_save":"syte_domain, syte_url from response.project","you_show_user":"updated HTTPS link"},{"when":"User stops dev preview","call":"POST https://your-syte-host.com/sycord/api/preview_stop","you_send":"{\"uuid\":\"<syte_uuid>\"}","you_save":"clear cached preview_url","you_show_user":"preview stopped state"}]},"endpoints":[{"method":"POST","path":"/sycord/api/project_connect","auth":true,"summary":"Create Syte project — returns uuid to save","request":{"content_type":"application/json","body":{"name":"string (required) — project name, used for subdomain slug","stack":"nextjs | python | javascript | html5 (default nextjs)","uuid":"string (optional) — your custom Syte project id","env_vars":"object (optional) — KEY=value pairs"}},"response":{"save_field":"uuid","example":{"ok":true,"uuid":"testproject-a1b2c3","message":"Empty project testproject-a1b2c3 created. Scaffolded: app/package.json, app/Dockerfile…","persist":{"save_uuid":true,"uuid":"testproject-a1b2c3","instruction":"Save uuid in your Sycord project database (e.g. projects.syte_uuid). Every follow-up call — upload, issue_deployment, container_get, domain — requires this uuid.","endpoints_using_uuid":["POST /sycord/api/upload — form field uuid","POST /sycord/api/issue_deployment — body.uuid","GET /sycord/api/container_get?uuid=","POST /sycord/api/domain — body.uuid","POST /sycord/api/preview_start — body.uuid","GET /sycord/api/preview_status?uuid=","POST /sycord/api/preview_stop — body.uuid"]},"project":{"uuid":"testproject-a1b2c3","name":"testproject","domain":"testproject.sycord.site","url":"https://testproject.sycord.site","stack":"nextjs","status":"created","port":3010,"workspace_path":"/var/syte/workspaces/testproject-a1b2c3","app_path":"/var/syte/workspaces/testproject-a1b2c3/app","created_at":"2026-07-04T18:00:00Z"},"subdomain_pattern":"{slug}.sycord.site","next_steps":{"upload":"POST /sycord/api/upload","preview":"POST /sycord/api/preview_start","deploy":"POST /sycord/api/issue_deployment","container":"GET /sycord/api/container_get?uuid=testproject-a1b2c3"}}}},{"method":"GET","path":"/sycord/api/container_get","auth":true,"summary":"Docker container status for a connected project","request":{"query":{"uuid":"string (required) — from project_connect"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","container_name":"syte-testproject-a1b2c3","exists":true,"running":true,"state":"running","image":"syte-testproject-a1b2c3","url":"https://testproject.sycord.site","domain":"testproject.sycord.site","host_port":3010,"status":"running"}}},{"method":"POST","path":"/sycord/api/upload","auth":true,"summary":"Upload a file into the project workspace","request":{"content_type":"multipart/form-data","fields":{"uuid":"string (required)","path":"string (required) e.g. app/app/page.tsx","file":"binary (required)"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","path":"app/app/page.tsx","bytes":1024,"message":"Uploaded 1024 bytes to app/app/page.tsx"}}},{"method":"POST","path":"/sycord/api/domain","auth":true,"summary":"Set production HTTPS domain (Caddy TLS)","request":{"content_type":"application/json","body":{"uuid":"string (required)","domain":"string (required) e.g. myapp.sycord.site"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","domain":"myapp.sycord.site","url":"https://myapp.sycord.site","message":"Domain set to myapp.sycord.site…"}}},{"method":"POST","path":"/sycord/api/issue_deployment","auth":true,"summary":"Docker build + deploy (background)","request":{"content_type":"application/json","body":{"uuid":"string (required)"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","message":"Deploy issued for testproject-a1b2c3…","stream_url":"/api/projects/testproject-a1b2c3/logs/stream?live=1","status":"deploying"}}},{"method":"POST","path":"/sycord/api/preview_start","auth":true,"summary":"Start fast dev preview (next dev / vite, HMR, ~5s)","request":{"content_type":"application/json","body":{"uuid":"string (required)"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","message":"Preview on https://previewk-testproject.sycord.site — ready (HMR live)","preview_url":"https://previewk-testproject.sycord.site","preview_domain":"previewk-testproject.sycord.site","preview_domain_url":"https://previewk-testproject.sycord.site","preview_direct_url":"http://203.0.113.10:4001","preview_ready":true,"preview_running":true,"preview_port":4001,"preview_status":"running","preview_stream_url":"/api/projects/testproject-a1b2c3/preview/logs/stream?live=1"}}},{"method":"GET","path":"/sycord/api/preview_status","auth":true,"summary":"Preview dev server status — poll until preview_ready=true","request":{"query":{"uuid":"string (required) — from project_connect"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","preview_url":"https://previewk-testproject.sycord.site","preview_domain":"previewk-testproject.sycord.site","preview_ready":true,"preview_running":true,"preview_port":4001,"preview_status":"running","preview_stream_url":"/api/projects/testproject-a1b2c3/preview/logs/stream?live=1"}}},{"method":"POST","path":"/sycord/api/preview_stop","auth":true,"summary":"Stop preview dev server","request":{"content_type":"application/json","body":{"uuid":"string (required)"}},"response":{"example":{"ok":true,"uuid":"testproject-a1b2c3","message":"Preview stopped","preview_running":false,"preview_ready":false,"preview_status":"stopped"}}},{"method":"GET","path":"/sycord/api/spec.json","auth":false,"summary":"This machine-readable specification"}]}