{"openapi": "3.1.0", "info": {"title": "Synsema Platform API", "version": "0.1.0", "description": "Deploy Synsema agents under a capability ceiling. Every /api/v1 route takes `Authorization: Bearer <token>`; get a token with POST /api/v1/auth/login or from Settings in the dashboard."}, "servers": [{"url": "https://synsema.com"}], "paths": {"/api/v1/auth/login": {"post": {"summary": "Exchange email and password for an API token", "responses": {"200": {"description": "{token, user}"}}, "parameters": [], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"email": {"type": "string", "description": "string"}, "password": {"type": "string", "description": "string"}}}}}}}}, "/api/v1/auth/device": {"post": {"summary": "Ask to be signed in from a browser: a terminal never handles a password", "responses": {"200": {"description": "{device_code, user_code, verify_url, expires_in, interval}"}}, "parameters": [], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "string (what to call this device)"}}}}}}}}, "/api/v1/auth/device/token": {"post": {"summary": "Poll until a person approves the code; the token is handed over once", "responses": {"200": {"description": "{status: pending} or {status: ok, token, user}"}}, "parameters": [], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"device_code": {"type": "string", "description": "string"}}}}}}}}, "/api/v1/me": {"get": {"summary": "The account behind the token", "responses": {"200": {"description": "{id, email, name, plan, admin, via}"}, "401": {"description": "missing or revoked token"}}, "parameters": [], "security": [{"bearer": []}]}}, "/api/v1/recipes": {"get": {"summary": "The recipe catalog, mirrored from github.com/synsema/recipes", "responses": {"200": {"description": "{items: [{slug, name, version, kind, entry, repo, ref, secrets, tagline, tagline_es, tagline_pt}]}"}}, "parameters": []}}, "/api/v1/projects": {"get": {"summary": "Your projects", "responses": {"200": {"description": "{items: [project]}"}, "401": {"description": "missing or revoked token"}}, "parameters": [], "security": [{"bearer": []}]}, "post": {"summary": "Create a project: from files (the package `syn deploy` sends), a recipe, a public GitHub repository, or one pasted entry file", "responses": {"200": {"description": "{project, ceiling, package, missing_secrets}"}, "401": {"description": "missing or revoked token"}}, "parameters": [], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "string"}, "files": {"type": "string", "description": "[{path, content, encoding: utf8 | base64}] (optional)"}, "recipe": {"type": "string", "description": "slug (optional)"}, "repo": {"type": "string", "description": "https://github.com/owner/repo (optional)"}, "ref": {"type": "string", "description": "branch or tag (optional, with repo)"}, "source": {"type": "string", "description": "string, the entry file (optional)"}, "entry": {"type": "string", "description": "string (optional)"}, "kind": {"type": "string", "description": "web | worker (optional)"}}}}}}}}, "/api/v1/projects/{id}": {"get": {"summary": "One project, its effective ceiling, package, missing secrets, deploys, and `blocked`: why it cannot deploy on this plan, or empty", "responses": {"200": {"description": "{project, ceiling, package, missing_secrets, blocked, deploys}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}, "delete": {"summary": "Delete the project; if it ever ran, the runner erases its container and volume first", "responses": {"200": {"description": "{ok, status}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/package": {"get": {"summary": "The current files of the project", "responses": {"200": {"description": "{entry, kind, package, files: [{path, content, encoding}]}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/deploys": {"post": {"summary": "Queue a deploy of the current package, or send a new one first. 409 with the reason when a `require` line or the memory is above the plan, or the plan's services or memory budget are full", "responses": {"200": {"description": "{deploy, ceiling, missing_secrets, url, logs}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"files": {"type": "string", "description": "[{path, content, encoding}] (optional, a new package)"}, "entry": {"type": "string", "description": "string (optional, with files)"}, "source": {"type": "string", "description": "string (optional, replaces the entry file)"}}}}}}}}, "/api/v1/projects/{id}/stop": {"post": {"summary": "Ask the runner to take the service down; the files and secrets stay", "responses": {"200": {"description": "{ok, status}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/deploys/{did}": {"get": {"summary": "One deploy and the ceiling it was given", "responses": {"200": {"description": "deploy"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "did", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/logs": {"get": {"summary": "Live logs as server-sent events (event: log)", "responses": {"200": {"description": "text/event-stream"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/logs/tail": {"get": {"summary": "Log lines after an id, as JSON (?after=<id>); what `syn logs` polls", "responses": {"200": {"description": "{items: [{id, at, line}], last}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/audit": {"get": {"summary": "Every capability check, declared versus done (?only=granted|denied, ?q=text in the capability, scope, origin or reason)", "responses": {"200": {"description": "{items: [{ts, capability, scope, granted, origin, why}]}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/secrets": {"get": {"summary": "The secrets set (names and fingerprints, never values) and the ones the manifest still needs", "responses": {"200": {"description": "{items: [{name, fingerprint, created_at}], missing: [name]}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/secrets/{name}": {"put": {"summary": "Set a secret (UPPER_SNAKE_CASE); the value is never returned", "responses": {"200": {"description": "{ok, name}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "name", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"value": {"type": "string", "description": "string"}}}}}}}}, "/api/v1/projects/{id}/env": {"get": {"summary": "The whole environment the container gets: variables with their value, secrets with their fingerprint, what is still missing, and whether a change waits for a redeploy", "responses": {"200": {"description": "{items: [{name, kind, value, fingerprint, created_at}], missing: [name], needs_redeploy}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/env/{name}": {"put": {"summary": "Set a variable or a secret; without kind, a name the program or the recipe declares as a secret is one, the rest are variables. The service picks it up on its next deploy", "responses": {"200": {"description": "{ok, name, kind}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "name", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"value": {"type": "string", "description": "string"}, "kind": {"type": "string", "description": "secret | variable (optional)"}}}}}}}, "delete": {"summary": "Remove one entry of the environment", "responses": {"200": {"description": "{ok, name}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "name", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/approvals": {"get": {"summary": "The human gates your running programs are waiting on (`approve`, `confirm`, `ask` reached under serve), and the ones decided lately", "responses": {"200": {"description": "{items: [{id, project_id, project, gate_id, type, message, status, expires_at, received_at}], recent: [...]}"}, "401": {"description": "missing or revoked token"}}, "parameters": [], "security": [{"bearer": []}]}}, "/api/v1/approvals/{id}": {"post": {"summary": "Decide one gate: {decision: true|false} for approve and confirm, {value} for ask. The runner hands it to the program with its one-time token; 409 when it was already decided or expired", "responses": {"200": {"description": "{ok, status: approved | denied | answered}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"decision": {"type": "string", "description": "true | false (approve, confirm)"}, "value": {"type": "string", "description": "string (ask)"}}}}}}}}, "/api/v1/projects/{id}/approvals": {"get": {"summary": "The gates of one project, pending and decided", "responses": {"200": {"description": "{items: [approval]}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/run": {"post": {"summary": "Run a job now (kind = job): the runner starts it in a fresh container on its next poll. 409 while a run is in progress or before the first deploy", "responses": {"200": {"description": "{run: {id, status, trigger, exit_code, output, created_at, started_at, finished_at}}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/runs": {"get": {"summary": "The runs of a job, newest first, and its schedule", "responses": {"200": {"description": "{items: [run], schedule}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/runs/{rid}": {"get": {"summary": "One run, with the tail of its output", "responses": {"200": {"description": "run"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "rid", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}]}}, "/api/v1/projects/{id}/schedule": {"put": {"summary": "The schedule of a job: five cron fields (minute hour day month weekday, UTC) or @hourly, @daily, @weekly, @monthly, @yearly; empty = only when asked", "responses": {"200": {"description": "{ok, schedule, means}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"cron": {"type": "string", "description": "string"}}}}}}}}, "/api/v1/projects/{id}/sleep": {"put": {"summary": "Idle minutes before a web service sleeps (its next request wakes it); 0 keeps it warm. Pro and up; the Free plan always sleeps after 10. Applies on the next deploy", "responses": {"200": {"description": "{ok, sleep_after, note}"}, "401": {"description": "missing or revoked token"}}, "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}], "security": [{"bearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"after_minutes": {"type": "string", "description": "number"}}}}}}}}}, "components": {"securitySchemes": {"bearer": {"type": "http", "scheme": "bearer"}}}}