{"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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/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://sycord.site/sycord/api/preview_stop","you_send":"{\"uuid\":\"<syte_uuid>\"}","you_save":"clear cached preview_url","you_show_user":"preview stopped state"}]}