{"openapi":"3.1.0","info":{"title":"Alf API","version":"1","description":"The REST API behind the Alf dashboard: everything the dashboard does, it does through these endpoints.\n\n## Getting a token\n\nLog in for an access token, then send it with every request:\n\n```bash\ncurl -s https://api.runalf.com/api/v1/auth/login \\\n  -H 'content-type: application/json' \\\n  -d '{\"email\":\"you@example.com\",\"password\":\"…\"}'\n\ncurl -s https://api.runalf.com/api/v1/auth/me -H \"authorization: Bearer $ACCESS_TOKEN\"\n```\n\nThe access token is valid for 15 minutes; `POST /api/v1/auth/refresh` trades the refresh token for a new pair. In this page, set the token under **Authentication** to try endpoints out.\n\n## Good to know\n\n- **Ids** look like `svc_…`, `prj_…`, `srv_…`: the prefix says what it is.\n- **Errors** are JSON: `{ \"error\": { \"code\", \"message\", \"details\" } }`, with the usual status codes (401 no or expired token, 403 not allowed, 404 not there or not yours, 409 conflict, 422 the request is wrong, and says how).\n- **Lists** come as `{ \"data\": [...] }`.\n- **Secrets** are write-only: their values never come back.\n\n## Coding agents\n\nAgents (Claude Code, Codex, …) don't use this API but the MCP endpoint at `https://api.runalf.com/mcp`, with an agent token or by signing in. See [the agent docs](https://runalf.com/docs/agents).\n\nThis reference is generated from the API's own router and validation schemas, so it describes what's deployed. The same document as JSON: [/openapi.json](https://api.runalf.com/openapi.json)."},"servers":[{"url":"https://api.runalf.com"}],"tags":[{"name":"Authentication","description":"Accounts and tokens. Log in for an access token (valid 15 minutes) and a refresh token; send the access token as `Authorization: Bearer …`."},{"name":"Organizations","description":"An organization owns projects and has members."},{"name":"Members","description":"Who belongs to an organization, and in which role (owner, admin, member)."},{"name":"Projects","description":"A project holds services that share a private network."},{"name":"Services","description":"One container each: running a published image, or built from code pushed to it. Variables and secrets, volumes. A static site (kind `site`) is built the same way, but never run: its image's site folder is served from the edge."},{"name":"Deployments","description":"Deploying, the history, rolling back, logs and metrics."},{"name":"One-click deploys","description":"Ready-made stacks from the public repository, deployed into a project as one group."},{"name":"Pushing code","description":"How code gets to Alf: the deploy tool (`deploy.sh`, the GitHub Action) packs a directory, says what it found (a docker-compose file or a Dockerfile), sends the code in parts and waits for the deployments. From CI with a deploy token, from a computer with a one-time link. There is no connection to GitHub to set up."},{"name":"Groups","description":"Frames around services that belong together on the project's canvas. Visual only."},{"name":"Domains","description":"Your own domains on a public service."},{"name":"Public ports","description":"TCP and UDP ports for databases and other services that don't speak HTTP, open to an allowlist."},{"name":"Servers","description":"The machines services run on. Alf runs them: shared ones in several regions, and private ones for a single organization."},{"name":"Snapshots","description":"Backups of volumes: a volume's files as one archive on its server. Taken by hand or on a schedule, restored, downloaded."},{"name":"Previews","description":"Throwaway copies of a project, for trying a change (a pull request in CI, an agent's work)."},{"name":"Databases","description":"Postgres, MySQL and Redis, added to a project in one click: private, always running, backed up every day, and wired to an app with one call."},{"name":"Buckets","description":"Files an app keeps outside its container, over S3: uploads, exports, assets. Any S3 client or SDK works with a bucket, at its endpoint with path-style addressing and region auto. A service gets a bucket's variables by reference (S3_BUCKET = ${{uploads.S3_BUCKET}}), so a new key reaches it with its next deploy. Billed like volumes, by what is stored."},{"name":"Computers","description":"A Linux machine of your own, in a project: a container with a shell and a disk at /workspace, isolated like any service here, with no address of its own. Either made for a coding agent (Debian 12 with Node 22, given Claude Code and Alf's tools as it starts) or an empty Debian, Ubuntu or Fedora. Its `id` is its service's. It runs until it is stopped, and is kept until it is deleted unless it was given a time to go."},{"name":"Devices","description":"A laptop, or any other machine, in one project's private network: a WireGuard peer of the servers the project runs on, with an address of its own (10.88.128.0/17), that reaches that project's containers and nothing else. `alf connect <project>` makes the key on the machine and registers its public half; the private half never leaves it. Each has an owner, a name and a time it stops working (or none). Revoking it takes it off every server at once."},{"name":"Notifications","description":"Slack, Discord, webhook or email when something happens."},{"name":"Usage","description":"What an organization used, by month."},{"name":"Agents","description":"Tokens and approvals for coding agents (which use the MCP endpoint at `/mcp`, not this API)."},{"name":"OAuth","description":"Signing coding agents in to the MCP endpoint (OAuth 2.1 with PKCE and dynamic client registration)."},{"name":"Platform","description":"The API itself."}],"paths":{"/health":{"get":{"tags":["Platform"],"summary":"Whether the API is up","operationId":"get_health","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/openapi.json":{"get":{"tags":["Platform"],"summary":"This reference, as an OpenAPI 3.1 document","operationId":"get_openapi_json","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/.well-known/oauth-protected-resource":{"get":{"tags":["OAuth"],"summary":"Protected resource metadata (RFC 9728)","operationId":"get_well_known_oauth_protected_resource","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/.well-known/oauth-protected-resource/mcp":{"get":{"tags":["OAuth"],"summary":"Protected resource metadata for /mcp","operationId":"get_well_known_oauth_protected_resource_mcp","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/.well-known/oauth-authorization-server":{"get":{"tags":["OAuth"],"summary":"Authorization server metadata (RFC 8414)","operationId":"get_well_known_oauth_authorization_server","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/.well-known/openid-configuration":{"get":{"tags":["OAuth"],"summary":"The same metadata, where OpenID clients look","operationId":"get_well_known_openid_configuration","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/oauth/authorize":{"get":{"tags":["OAuth"],"summary":"Start a sign-in","description":"Where a client sends the browser (with client_id, redirect_uri, code_challenge, state…): it's sent on to the dashboard's consent page.","operationId":"get_oauth_authorize","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/oauth/register":{"post":{"tags":["OAuth"],"summary":"Register a client (RFC 7591)","description":"Dynamic registration: MCP clients do this by themselves.","operationId":"post_oauth_register","responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/oauth/token":{"post":{"tags":["OAuth"],"summary":"Exchange a code or refresh token","operationId":"post_oauth_token","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/oauth/revoke":{"post":{"tags":["OAuth"],"summary":"Revoke a token","operationId":"post_oauth_revoke","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/signup":{"post":{"tags":["Authentication"],"summary":"Create an account","description":"Creates the person and logs them in. With `organization_name`, also their first organization; without it, they make one next (POST /organizations) or join one through an invitation link (POST /invitations/{token}/accept).","operationId":"post_auth_signup","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254},"password":{"type":"string","minLength":10,"maxLength":200},"name":{"type":"string","maxLength":100},"organization_name":{"type":"string","minLength":1,"maxLength":100}},"required":["email","password"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/login":{"post":{"tags":["Authentication"],"summary":"Log in","description":"Returns an `access_token` (15 minutes) and a `refresh_token`. With two-factor authentication on, it returns `two_factor_required: true` and a `ticket` instead: send it with a code to `POST /auth/login/two-factor`.","operationId":"post_auth_login","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":1}},"required":["email","password"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/login/two-factor":{"post":{"tags":["Authentication"],"summary":"Log in: the second step","description":"The `ticket` from `POST /auth/login` and a `code`: six digits from the authenticator app, or one of the recovery codes (each works once). Returns the tokens. A ticket lasts 10 minutes and takes 5 tries; wrong codes count as failed sign-ins, and after 10 of them in 15 minutes the address waits.","operationId":"post_auth_login_two_factor","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ticket":{"type":"string","minLength":1,"maxLength":200},"code":{"type":"string","minLength":1,"maxLength":40}},"required":["ticket","code"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Get a new access token","description":"Trades a refresh token for a new pair. The old refresh token stops working.","operationId":"post_auth_refresh","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refresh_token":{"type":"string","minLength":1}},"required":["refresh_token"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/logout":{"post":{"tags":["Authentication"],"summary":"Log out","description":"Ends the session the refresh token belongs to.","operationId":"post_auth_logout","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refresh_token":{"type":"string","minLength":1}},"required":["refresh_token"]}}}},"responses":{"204":{"description":"Done. Nothing to return."},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/me":{"get":{"tags":["Authentication"],"summary":"Who the token belongs to","description":"The person and the organizations they're in. `email_verified` false means the account confirms its address first: until it does, everything but this, its sign-ins, `POST /auth/verify-email/send` and accepting an invitation answers 403 `email_unverified`.","operationId":"get_auth_me","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]},"patch":{"tags":["Authentication"],"summary":"Change your settings","description":"`theme`: how the dashboard and the docs look for you, `light`, `dark` or `system` (as your device is set, the default). Every device you sign in on follows it. Answers with the same as `GET /auth/me`.","operationId":"patch_auth_me","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"theme":{"type":"string","enum":["system","light","dark"]}},"required":["theme"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]},"delete":{"tags":["Authentication"],"summary":"Delete your account","description":"For good, with your password asked again. Refused with 409 `account_deletion_blocked` while you're the only owner of an organization (see `GET /auth/me/deletion`), and with 403 for a wrong password. You leave every organization you're in; your own agent tokens, the agents signed in as you and every sign-in stop at once. Tokens you issued to an organization's service account keep working, and what you did stays in the organizations' activity records, under your address.","operationId":"delete_auth_me","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","minLength":1,"maxLength":200}},"required":["password"]}}}},"responses":{"204":{"description":"Done. Nothing to return."},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/me/deletion":{"get":{"tags":["Authentication"],"summary":"Whether your account can be deleted","description":"`allowed`, and for each organization you're in what happens to it: you leave it, unless you're its only owner. Then `blocker` says what to do first: `needs_an_owner` (other people are in it: make one of them an owner) or `only_you` (nobody else is: delete the organization). An organization already being deleted never blocks. Also how many of your agent tokens, agent connections and sign-ins stop working.","operationId":"get_auth_me_deletion","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/providers":{"get":{"tags":["Authentication"],"summary":"Ways to sign in","description":"Whether signing in with GitHub (`github`) and with Google (`google`) can be used here.","operationId":"get_auth_providers","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/two-factor":{"get":{"tags":["Authentication"],"summary":"Two-factor authentication","description":"Whether it's on (`enabled`, `enabled_at`) and how many recovery codes are left.","operationId":"get_auth_two_factor","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/two-factor/setup":{"post":{"tags":["Authentication"],"summary":"Start setting up two-factor authentication","description":"A new `secret` (base32) and its `otpauth_url` for an authenticator app to scan. Nothing changes until `POST /auth/two-factor/enable` confirms it with a code. 409 if it's on.","operationId":"post_auth_two_factor_setup","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/two-factor/enable":{"post":{"tags":["Authentication"],"summary":"Turn on two-factor authentication","description":"A code from the app and your password. Returns ten `recovery_codes`: shown this once, each signs in once without the app.","operationId":"post_auth_two_factor_enable","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":40},"password":{"type":"string","minLength":1,"maxLength":200}},"required":["code","password"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/two-factor/disable":{"post":{"tags":["Authentication"],"summary":"Turn off two-factor authentication","description":"Your password and a code from the app, or a recovery code.","operationId":"post_auth_two_factor_disable","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":40},"password":{"type":"string","minLength":1,"maxLength":200}},"required":["code","password"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/two-factor/recovery-codes":{"post":{"tags":["Authentication"],"summary":"New recovery codes","description":"Your password and a code: ten new `recovery_codes`, shown this once. The old ones stop working.","operationId":"post_auth_two_factor_recovery_codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":40},"password":{"type":"string","minLength":1,"maxLength":200}},"required":["code","password"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/verify-email":{"post":{"tags":["Authentication"],"summary":"Confirm an email address","description":"With the token from the link in the confirmation email; needs no sign-in. Using a link again is fine; one past its 3 days answers 410.","operationId":"post_auth_verify_email","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":1,"maxLength":200}},"required":["token"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/verify-email/send":{"post":{"tags":["Authentication"],"summary":"Send the confirmation email again","description":"At most once a minute (429 otherwise); 503 when the email couldn't be sent (try again). `verified` true when there's nothing left to confirm.","operationId":"post_auth_verify_email_send","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/password/forgot":{"post":{"tags":["Authentication"],"summary":"Ask for a link to choose a new password","description":"Emails one, valid for an hour, if an account has this address. Answers 202 either way, so it doesn't tell who has an account.","operationId":"post_auth_password_forgot","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254}},"required":["email"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/password/reset":{"post":{"tags":["Authentication"],"summary":"Choose a new password","description":"With the token from that link. Every sign-in of the account ends, and its address counts as confirmed.","operationId":"post_auth_password_reset","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":1,"maxLength":200},"password":{"type":"string","minLength":10,"maxLength":200}},"required":["token","password"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/sign-ins":{"get":{"tags":["Authentication"],"summary":"Where you're signed in","description":"Every sign-in that can still be used: a browser, or the command-line tool on a machine (`kind`), what it is (`client`), its machine's `name`, where it was last used from, and when. `current` is the one this request comes from.","operationId":"get_auth_sign_ins","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/sign-ins/{sign_in_id}":{"delete":{"tags":["Authentication"],"summary":"Sign one out","description":"Its refresh token stops working at once; an access token it already has works out its few minutes.","operationId":"delete_auth_sign_ins_sign_in_id","parameters":[{"name":"sign_in_id","in":"path","required":true,"schema":{"type":"string"},"description":"The sign in's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/sign-ins/sign-out-others":{"post":{"tags":["Authentication"],"summary":"Sign out everywhere else","description":"Every sign-in except the one this request comes from.","operationId":"post_auth_sign_ins_sign_out_others","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/auth/cli":{"post":{"tags":["Authentication"],"summary":"Start signing the command-line tool in","description":"Answers with a `user_code` to show, a `verify_url` to open, and a `device_code` to ask with. Someone signed in to the dashboard opens the link, checks the code and approves; meanwhile the tool asks `POST /auth/cli/poll` every `interval` seconds. The request runs out after `expires_in` seconds.","operationId":"post_auth_cli","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/auth/cli/poll":{"post":{"tags":["Authentication"],"summary":"Ask whether the tool is let in","description":"`status`: `pending` (ask again), `denied`, `expired`, or `approved` with the tokens of a new sign-in, handed out once. Asking again after that is refused.","operationId":"post_auth_cli_poll","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"device_code":{"type":"string","minLength":1,"maxLength":200}},"required":["device_code"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/organizations":{"get":{"tags":["Organizations"],"summary":"List my organizations","operationId":"get_organizations","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Organizations"],"summary":"Create an organization","operationId":"post_organizations","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","minLength":2,"maxLength":30}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}":{"get":{"tags":["Organizations"],"summary":"Get an organization","operationId":"get_organizations_organization_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"patch":{"tags":["Organizations"],"summary":"Change an organization","description":"Admins. Also where the GitHub token for private repositories is set.","operationId":"patch_organizations_organization_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","minLength":2,"maxLength":30}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Organizations"],"summary":"Delete an organization","description":"Owners. Everything stops at once: its containers are removed from the servers and its apps stop being served, and until it's undone nothing of it can be changed. A week later what's left is purged for good (volumes and their snapshots, custom domains, pushed code); its invoices are kept as accounting records. `confirm` is the organization's address part, typed back. Refused while an invoice is unpaid.","operationId":"delete_organizations_organization_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirm":{"type":"string","minLength":1},"reason":{"type":"string","maxLength":500}},"required":["confirm"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/error-page":{"get":{"tags":["Organizations"],"summary":"Get the organization's own error page","description":"The HTML its visitors get instead of Alf's page when one of its apps can't answer (`html` is null when it has none), the `{{fields}}` that are filled in, and how large it may be.","operationId":"get_organizations_organization_id_error_page","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"put":{"tags":["Organizations"],"summary":"Set the organization's own error page","description":"Admins. A whole HTML document, shown to browsers for every app of the organization that can't answer: at its Alf addresses and its own domains. `{{title}}`, `{{message}}`, `{{status}}`, `{{code}}`, `{{host}}` and `{{retry_after}}` in it are filled in with what happened. The status code and headers stay what they were, and programs keep getting the facts as JSON or text. Not used where there is no app to belong to (an address nothing is deployed at) or while the organization is paused.","operationId":"put_organizations_organization_id_error_page","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"html":{"type":"string"}},"required":["html"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Organizations"],"summary":"Remove the organization's own error page","description":"Admins. Visitors get Alf's pages again.","operationId":"delete_organizations_organization_id_error_page","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/restore":{"post":{"tags":["Organizations"],"summary":"Undo a deletion","description":"Owners, until the week is up: the organization is itself again and its services come back up.","operationId":"post_organizations_organization_id_restore","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/registries":{"get":{"tags":["Organizations"],"summary":"List the organization's registry logins","description":"Its accounts at container registries, one per registry (`server`, like ghcr.io or docker.io), by username. Passwords are never shown.","operationId":"get_organizations_organization_id_registries","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/registries/{server}":{"put":{"tags":["Organizations"],"summary":"Set a registry login","description":"Admins. The organization's login for a registry (`:server` is its host, as its images start with: ghcr.io, registry.example.com:5000, docker.io for Docker Hub): what its services' private images are pulled with, and its builds' private FROM images. The registry is asked first, and a login it turns away isn't kept (`check` says `ok`, or `unchecked` when the registry couldn't say). The password is usually a token that may read packages; it's encrypted and never shown again. An image pulled with a login is kept under a name of the organization's own on its server, so no other organization's service gets it by naming it.","operationId":"put_organizations_organization_id_registries_server","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"server","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","minLength":1,"maxLength":200},"password":{"type":"string","minLength":1,"maxLength":4096}},"required":["username","password"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Organizations"],"summary":"Remove a registry login","description":"Admins. Images already on servers stay until they're no longer used; new pulls from that registry go without a login.","operationId":"delete_organizations_organization_id_registries_server","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"server","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/members":{"get":{"tags":["Members"],"summary":"List members","operationId":"get_organizations_organization_id_members","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Members"],"summary":"Add a member, or invite them","description":"Admins. Someone with an account is added at once (`added: true`). Someone without one is invited (`invited: true`): the answer has a `link` that is shown only this once, and is emailed too where the platform sends email (`emailed`). Opening it is how they sign up and join; it works once, for 14 days.","operationId":"post_organizations_organization_id_members","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","member"],"default":"member"}},"required":["email"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/invitations":{"get":{"tags":["Members"],"summary":"Invitations nobody has used yet","description":"Admins. Their links aren't kept, so they aren't here.","operationId":"get_organizations_organization_id_invitations","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/invitations/{invitation_id}":{"delete":{"tags":["Members"],"summary":"Withdraw an invitation","description":"Admins. Its link stops working.","operationId":"delete_organizations_organization_id_invitations_invitation_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string"},"description":"The invitation's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/invitations/{token}":{"get":{"tags":["Members"],"summary":"What an invitation link is for","description":"Needs no account: the token is the link's secret. Says which organization, as which role, and whether it can still be used.","operationId":"get_invitations_token","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/invitations/{token}/accept":{"post":{"tags":["Members"],"summary":"Join with an invitation link","description":"As whoever is signed in. A link works once.","operationId":"post_invitations_token_accept","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/organizations/{organization_id}/members/{member_id}":{"patch":{"tags":["Members"],"summary":"Change a member's role","operationId":"patch_organizations_organization_id_members_member_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"member_id","in":"path","required":true,"schema":{"type":"string"},"description":"The member's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["owner","admin","member"]}},"required":["role"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Members"],"summary":"Remove a member","operationId":"delete_organizations_organization_id_members_member_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"member_id","in":"path","required":true,"schema":{"type":"string"},"description":"The member's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/projects":{"get":{"tags":["Projects"],"summary":"List projects","operationId":"get_organizations_organization_id_projects","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Projects"],"summary":"Create a project","operationId":"post_organizations_organization_id_projects","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"slug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","minLength":1,"maxLength":30}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}":{"get":{"tags":["Projects"],"summary":"Get a project","description":"With its services (each with its state on every server it runs on) and its groups.","operationId":"get_projects_project_id","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"patch":{"tags":["Projects"],"summary":"Change a project","description":"Renaming its address part moves all its services' addresses.","operationId":"patch_projects_project_id","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"slug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","minLength":1,"maxLength":30},"agents_need_approval":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Projects"],"summary":"Delete a project","description":"With all its services. Admins (members may end a preview).","operationId":"delete_projects_project_id","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/alf.toml":{"get":{"tags":["Projects"],"summary":"Get a project as alf.toml","description":"The project written out as an alf.toml file (TOML text): its services with every setting that isn't the default, their variables, volumes, scheduled commands and domains, and its databases, buckets and computers. Secrets are there by name only. Deploying the file back changes nothing.","operationId":"get_projects_project_id_alf_toml","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/smartalfred.toml":{"get":{"tags":["Projects"],"summary":"Get a project as alf.toml (its first name)","description":"The same as `GET /projects/:project_id/alf.toml`, at the file's first name, smartalfred.toml.","operationId":"get_projects_project_id_smartalfred_toml","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/services":{"post":{"tags":["Services"],"summary":"Create a service","description":"Creates the service. `source` is `image` (give `image`: it's deployed at once) or `upload` (built from code: it waits for the first push). Pushing code creates services by itself, from a docker-compose file or a Dockerfile: see Pushing code. Public services get an HTTPS address; private ones are reachable inside the project as `name:port`.","operationId":"post_projects_project_id_services","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"slug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","minLength":1,"maxLength":30},"source":{"type":"string","enum":["image","upload"],"default":"image"},"image":{"type":"string","minLength":1,"maxLength":300,"pattern":"^[a-z0-9][a-z0-9._\\-/:@]*$"},"dockerfile":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\/)(?!.*(^|\\/)\\.\\.(\\/|$))[A-Za-z0-9._\\-/ ]*$","default":"Dockerfile"},"build_context":{"type":"string","maxLength":300,"pattern":"^(?!\\/)(?!.*(^|\\/)\\.\\.(\\/|$))[A-Za-z0-9._\\-/ ]*$","default":""},"port":{"type":["integer","null"],"minimum":1,"maximum":65535},"env":{"type":"object","additionalProperties":{"type":"string","maxLength":32768},"default":{}},"sleep_after_s":{"type":"integer","minimum":0,"maximum":604800},"always_on":{"type":"boolean"},"size":{"type":"string","enum":["xs","s","m","l","xl","2xl","3xl","4xl","5xl"]},"memory_mb":{"type":["integer","null"],"minimum":16,"maximum":262144},"cpus":{"type":["number","null"],"minimum":0.05,"maximum":128},"instances":{"type":"integer","minimum":1,"maximum":10,"default":1},"region_instances":{"type":["object","null"],"additionalProperties":{"type":"integer","minimum":0,"maximum":10}},"regions":{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20},"public":{"type":"boolean","default":true},"max_replicas":{"type":"integer","minimum":1,"maximum":20,"default":1},"target_rps":{"type":"number","minimum":0.1,"maximum":100000,"default":50},"command":{"anyOf":[{"anyOf":[{"type":"array","items":{"type":"string","maxLength":4000},"maxItems":100},{"type":"string","maxLength":8000}]},{"type":"null"}]},"entrypoint":{"anyOf":[{"anyOf":[{"type":"array","items":{"type":"string","maxLength":4000},"maxItems":100},{"type":"string","maxLength":8000}]},{"type":"null"}]},"release_command":{"anyOf":[{"anyOf":[{"type":"array","items":{"type":"string","maxLength":4000},"maxItems":100},{"type":"string","maxLength":8000}]},{"type":"null"}]},"health_path":{"type":["string","null"],"maxLength":200,"pattern":"^\\/[^\\s]*$"},"zero_downtime":{"type":"boolean"},"group_id":{"type":["string","null"],"maxLength":100},"kind":{"type":"string","enum":["container","site"],"default":"container"},"site_dir":{"type":["string","null"],"maxLength":300,"pattern":"^\\/[^\\0]*$"},"site_spa":{"type":"boolean","default":false},"secrets":{"type":"object","additionalProperties":{"type":"string","maxLength":32768},"default":{}},"volumes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"mount_path":{"type":"string","maxLength":300,"pattern":"^\\/[^\\0]*$"},"size_gb":{"type":"integer","minimum":1,"maximum":4096},"grow_up_to_gb":{"type":"integer","minimum":1,"maximum":4096}},"required":["name","mount_path"]},"maxItems":10,"default":[]}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}":{"get":{"tags":["Services"],"summary":"Get a service","description":"Its settings, address, variables (secrets by name only), current deployment and state per server.","operationId":"get_services_service_id","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"patch":{"tags":["Services"],"summary":"Change a service","description":"Any subset of the fields. A new image or repository starts a deployment, and so does a change to the container (port, command, public). A new `size` is given to its containers as they run: nothing restarts. Variables and secrets have their own endpoint.","operationId":"patch_services_service_id","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"slug":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","minLength":1,"maxLength":30},"source":{"type":"string","enum":["image","upload"]},"image":{"type":"string","minLength":1,"maxLength":300,"pattern":"^[a-z0-9][a-z0-9._\\-/:@]*$"},"dockerfile":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\/)(?!.*(^|\\/)\\.\\.(\\/|$))[A-Za-z0-9._\\-/ ]*$"},"build_context":{"type":"string","maxLength":300,"pattern":"^(?!\\/)(?!.*(^|\\/)\\.\\.(\\/|$))[A-Za-z0-9._\\-/ ]*$"},"port":{"type":["integer","null"],"minimum":1,"maximum":65535},"sleep_after_s":{"type":"integer","minimum":0,"maximum":604800},"always_on":{"type":"boolean"},"size":{"type":"string","enum":["xs","s","m","l","xl","2xl","3xl","4xl","5xl"]},"memory_mb":{"type":["integer","null"],"minimum":16,"maximum":262144},"cpus":{"type":["number","null"],"minimum":0.05,"maximum":128},"instances":{"type":"integer","minimum":1,"maximum":10},"region_instances":{"type":["object","null"],"additionalProperties":{"type":"integer","minimum":0,"maximum":10}},"regions":{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20},"public":{"type":"boolean"},"max_replicas":{"type":"integer","minimum":1,"maximum":20},"target_rps":{"type":"number","minimum":0.1,"maximum":100000},"command":{"anyOf":[{"anyOf":[{"type":"array","items":{"type":"string","maxLength":4000},"maxItems":100},{"type":"string","maxLength":8000}]},{"type":"null"}]},"entrypoint":{"anyOf":[{"anyOf":[{"type":"array","items":{"type":"string","maxLength":4000},"maxItems":100},{"type":"string","maxLength":8000}]},{"type":"null"}]},"release_command":{"anyOf":[{"anyOf":[{"type":"array","items":{"type":"string","maxLength":4000},"maxItems":100},{"type":"string","maxLength":8000}]},{"type":"null"}]},"health_path":{"type":["string","null"],"maxLength":200,"pattern":"^\\/[^\\s]*$"},"zero_downtime":{"type":"boolean"},"group_id":{"type":["string","null"],"maxLength":100},"kind":{"type":"string","enum":["container","site"]},"site_dir":{"type":["string","null"],"maxLength":300,"pattern":"^\\/[^\\0]*$"},"site_spa":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Services"],"summary":"Delete a service","description":"Its containers are removed from its servers, and its volumes' data with them.","operationId":"delete_services_service_id","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/activity":{"get":{"tags":["Services"],"summary":"What happened to a service","description":"Newest first: each deployment (`deploy` when it started, `live`, `failed` with why) and every change a person or an agent made to the service, its deployments, volumes and ports (`change`, with the route or tool as `action`). Agents' read-only tools aren't listed.","operationId":"get_services_service_id_activity","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/move":{"post":{"tags":["Services"],"summary":"Move a service to another region, with its data","description":"For a service with volumes: a server in `region` with the room for it is picked; the service is stopped, each volume is snapshotted with a copy off the server (a computer's filesystem too), it's put on the new server, the snapshots are restored there, and it starts. Its regions become that one. `move` on the service says how far it is. If its server is away, its data comes from each volume's newest copy off the server; without one, `without_data: true` moves it anyway, empty. Changing a service with a volume's `regions` to another one does the same.","operationId":"post_services_service_id_move","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"region":{"type":"string","minLength":1,"maxLength":120},"without_data":{"type":"boolean"}},"required":["region"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/variables":{"put":{"tags":["Services"],"summary":"Stage variables and secrets","description":"The whole set: variables with their values; secrets with a new value, or `null` to keep the value they have (a secret left out is removed). Nothing changes for the running service until the staged set is deployed.","operationId":"put_services_service_id_variables","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"env":{"type":"object","additionalProperties":{"type":"string","maxLength":32768}},"secrets":{"type":"object","additionalProperties":{"type":["string","null"],"maxLength":32768},"default":{}}},"required":["env"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/variables/deploy":{"post":{"tags":["Services"],"summary":"Deploy the staged variables","description":"Makes them live and starts a deployment.","operationId":"post_services_service_id_variables_deploy","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/variables/staged":{"delete":{"tags":["Services"],"summary":"Discard the staged variables","operationId":"delete_services_service_id_variables_staged","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/volumes":{"post":{"tags":["Services"],"summary":"Add a volume","description":"Data at `mount_path` then survives deploys. A service with a volume runs on one server.","operationId":"post_services_service_id_volumes","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"mount_path":{"type":"string","maxLength":300,"pattern":"^\\/[^\\0]*$"},"size_gb":{"type":"integer","minimum":1,"maximum":4096},"grow_up_to_gb":{"type":"integer","minimum":1,"maximum":4096}},"required":["name","mount_path"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/volumes/{volume_id}":{"patch":{"tags":["Services"],"summary":"Change a volume: its size, or whether it grows by itself","description":"A volume has a size: what it may hold, set aside on its server's disk and billed whether it's full or not. Reaching it stops the service, so it can't fill the server's disk and take the other services on it down. `size_gb` changes it: bigger, as far as its server has room and its plan allows, or smaller, down to what it holds (`smaller_down_to_bytes` on the volume). `room_for_gb` makes room before writing that much at once (an import): what it holds, plus that and a quarter again. `auto_grow` (off unless turned on; `grow_up_to_gb` turns it on) has it grow by itself past 80% full, half as much again, live, up to `grow_up_to_gb` (null: as far as the plan allows), billed by its size as it grows. It takes effect at once, with no deployment and no downtime.","operationId":"patch_volumes_volume_id","parameters":[{"name":"volume_id","in":"path","required":true,"schema":{"type":"string"},"description":"The volume's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"size_gb":{"type":"integer","minimum":1,"maximum":4096},"room_for_gb":{"type":"number","minimum":0,"maximum":4096},"auto_grow":{"type":"boolean"},"grow_up_to_gb":{"type":["integer","null"],"minimum":1,"maximum":4096}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Services"],"summary":"Delete a volume","description":"And its data, on the servers that have it.","operationId":"delete_volumes_volume_id","parameters":[{"name":"volume_id","in":"path","required":true,"schema":{"type":"string"},"description":"The volume's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/stop":{"post":{"tags":["Services"],"summary":"Switch a service off","description":"Stops it and keeps it stopped: its address answers with a page saying so, always-on doesn't start it, and a crash isn't restarted. Nothing is deleted — its container, volumes, data and address are kept, and while it is off its size isn't billed (its volumes still are, by their size). This is not sleeping, which ends with the next request.","operationId":"post_services_service_id_stop","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/start":{"post":{"tags":["Services"],"summary":"Switch a service on again","description":"Starts a stopped service, with the container and filesystem it was stopped with. One that sleeps goes back to waking on its next request; an always-on one starts now.","operationId":"post_services_service_id_start","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/servers":{"get":{"tags":["Servers"],"summary":"List servers","description":"Where the organization's services run or can run: Alf's shared servers and any private ones it has. For each: a name, a place, whether it's online, whether it takes new services right now (`accepting`; a busy server takes none until its load goes down), and which of the organization's services run there. Services are placed by region (`regions` on a service), never on a server by name.","operationId":"get_organizations_organization_id_servers","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/regions":{"get":{"tags":["Servers"],"summary":"Regions","description":"Where the organization's services can run: each region (a server's location, like \"Falkenstein, DE\"), how many servers it has, how many are online and taking new services, and, with ?project_id=, whether that project's services can go there. Give a service `regions` to keep it to some.","operationId":"get_organizations_organization_id_regions","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/deployments":{"get":{"tags":["Deployments"],"summary":"List a service's deployments","operationId":"get_services_service_id_deployments","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/restart":{"post":{"tags":["Deployments"],"summary":"Restart a service","description":"Fresh containers of what runs now, nothing else changed: an image is pulled again by its tag, pushed code is built again. Staged variables stay staged.","operationId":"post_services_service_id_restart","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/deploy":{"post":{"tags":["Deployments"],"summary":"Deploy now","description":"Image services pull their image again; services built from code rebuild the code pushed last. Staged variable and secret changes go live with it, unless `staged` is `keep`; the deployment's `config_note` says which happened. With `hold`, the new version is started beside the live one as a candidate and waits to be promoted (public services without a volume).","operationId":"post_services_service_id_deploy","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"staged":{"type":"string","enum":["apply","keep"]},"hold":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/deployments/{deployment_id}/promote":{"post":{"tags":["Deployments"],"summary":"Promote a candidate","description":"Makes a healthy candidate (`candidate: ready`) the live version: requests switch to it and the old version is removed. The staged config it ran with goes live too.","operationId":"post_deployments_deployment_id_promote","parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string"},"description":"The deployment's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/deployments/{deployment_id}/discard":{"post":{"tags":["Deployments"],"summary":"Discard a candidate","description":"Removes it. The live version was never touched.","operationId":"post_deployments_deployment_id_discard","parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string"},"description":"The deployment's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/deployments/{deployment_id}":{"get":{"tags":["Deployments"],"summary":"Get a deployment","description":"Its status (queued, building, built, live, failed, superseded) and its log. `config_note` says what it did with staged variables; `failure` says why it failed (`kind`: exited, port, health, start or build; with the container's last output); `candidate` is pending, ready, promoted or discarded for a version held beside the live one.","operationId":"get_deployments_deployment_id","parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string"},"description":"The deployment's id."},{"name":"log_tail","in":"query","required":false,"description":"Only the last this many characters of `log` (up to 65536), with `log_size` its whole length: for following a build as it goes.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/rollback":{"post":{"tags":["Deployments"],"summary":"Roll a service back","description":"Deploys an earlier version again (the same image or code, today's variables and secrets): `deployment_id` if it's given, else the newest deployment that was live before the current one. Answers with the new deployment and the one it goes back to (`back_to`).","operationId":"post_services_service_id_rollback","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deployment_id":{"type":"string","maxLength":64}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/deployments/{deployment_id}/redeploy":{"post":{"tags":["Deployments"],"summary":"Roll back to a deployment","description":"Deploys that image or commit again, as a new deployment.","operationId":"post_deployments_deployment_id_redeploy","parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string"},"description":"The deployment's id."}],"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/logs":{"get":{"tags":["Deployments"],"summary":"Read a service's logs","description":"The container's recent output, read live from one of its servers.","operationId":"get_services_service_id_logs","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."},{"name":"tail","in":"query","required":false,"description":"How many lines from the end (default 300, at most 2000).","schema":{"type":"string"}},{"name":"server","in":"query","required":false,"description":"The server to read from, when it runs on several.","schema":{"type":"string"}},{"name":"container","in":"query","required":false,"description":"Which of its containers there: 1 its own (the default), 2 and up one added under load (`instances[].containers`).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"text/plain":{"schema":{"type":"string"}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/metrics":{"get":{"tags":["Deployments"],"summary":"A service's metrics","description":"Requests, errors, response times, wake-ups, transfer and replicas, in buckets over the range. Memory and CPU are per container (the most any held, what they used on average), as its size limits them. With `server` and `container`, one container's alone (1 its own, 2 and up added under load: `instances[].containers`).","operationId":"get_services_service_id_metrics","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."},{"name":"range","in":"query","required":false,"description":"The time span: 1h, 6h, 24h or 7d.","schema":{"type":"string"}},{"name":"server","in":"query","required":false,"description":"Only this server's share.","schema":{"type":"string"}},{"name":"container","in":"query","required":false,"description":"With server: only this container (1 its own, 2 and up added under load).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/requests":{"get":{"tags":["Deployments"],"summary":"A service's newest requests","description":"Each request it answered lately (its servers keep the newest few hundred in memory): when, the method and path (without the query string), the status, how long it took and how big the answer was. Requests from the project's own services are there too (`internal`), with the service that asked (`from`) and a gRPC call's `grpc_status`. Oldest first. To follow along, pass the answer's cursor back.","operationId":"get_services_service_id_requests","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."},{"name":"cursor","in":"query","required":false,"description":"Where the last answer left off (its cursor): only what came after.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"At most this many, the newest (default 100, at most 500).","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Only these: a class (5xx, 4xx), a status (404), or error (4xx and 5xx).","schema":{"type":"string"}},{"name":"path","in":"query","required":false,"description":"Only requests whose path has this in it.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Only these: internet, inside (from the project's own services), or one service's name.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/connections":{"get":{"tags":["Deployments"],"summary":"A service's newest connections from inside its project","description":"The connections the project's own services made to it that weren't HTTP (a database's protocol, TLS, a port of its own), so have no requests of their own: when each ended, which service made it, the port, how long it was open, the bytes each way, and how it ended (closed, reset, refused, not woken). Its servers keep the newest few hundred in memory. Oldest first; to follow along, pass the answer's cursor back.","operationId":"get_services_service_id_connections","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."},{"name":"cursor","in":"query","required":false,"description":"Where the last answer left off (its cursor): only what came after.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"At most this many, the newest (default 100, at most 200).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/diagnosis":{"get":{"tags":["Deployments"],"summary":"What's wrong with a service","description":"Why it's down or slow, as far as Alf knows: its state, a failed newest deploy (with the failure's hint), running out of memory, a full or stuck volume, its error rate and slowness over the last hour, slow wakes, domains that don't work, and variable changes waiting for a deploy. Each finding is a problem or a warning, with what to do next. No findings: nothing looks wrong.","operationId":"get_services_service_id_diagnosis","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/metrics/now":{"get":{"tags":["Deployments"],"summary":"A project's services as they are now","description":"For a live view: each service's newest reported minute (CPU, memory and their limits, containers, seconds awake) and its last five minutes of requests (count, 5xx, slowest p95). Servers report a minute once it's over.","operationId":"get_projects_project_id_metrics_now","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/push":{"post":{"tags":["Pushing code"],"summary":"Start a push","description":"Plans a push and answers with the plan and the address to send the code to (`url`, the push's own key). Nothing is created or deployed until the push is completed. The body is the text of the docker-compose file the code has, or empty for a Dockerfile. With a compose file, every service in it is planned: `build:` ones are built from the pushed code, `image:` ones run that image; services the project already has get the new code (their settings stay), new ones are created. With a Dockerfile, one service is planned. With alf.toml, send both files as JSON (`{ \"config\": <alf.toml>, \"compose\": <compose file> }`) or as a form with those fields: the file's services are planned (with their settings, variables and secrets), and the databases, buckets and computers it names that the project hasn't got are made. `asks` lists the secrets it leaves to a person that have no value yet. `conflicts` lists the settings changed in the dashboard since the file last set them, which the file would change back: each stays as the dashboard has it unless the completion names it in `use_file` (or `config_wins` is set). Authorization: a deploy token (`Bearer sat_…`) or a login.","operationId":"post_push","parameters":[{"name":"project","in":"query","required":false,"description":"The project: its id, name or address part. Not needed with a token made for one project, nor when alf.toml names it.","schema":{"type":"string"}},{"name":"organization","in":"query","required":false,"description":"The organization the project is in, for a login naming the project by its name (alf.toml's organization says it too).","schema":{"type":"string"}},{"name":"base","in":"query","required":false,"description":"Where alf.toml is in the code, from its root (a monorepo's app, like `apps/web`): the file's paths start there.","schema":{"type":"string"}},{"name":"preview","in":"query","required":false,"description":"Push to the project's preview of this name instead (like `pr-12`); it's made if it isn't there.","schema":{"type":"string"}},{"name":"compose","in":"query","required":false,"description":"The compose file's path in the code (default `docker-compose.yml`): build contexts are relative to it.","schema":{"type":"string"}},{"name":"found","in":"query","required":false,"description":"A Dockerfile's path in the code, as the tool found it. Used for a new service, and for one that has had no code yet.","schema":{"type":"string"}},{"name":"dockerfile","in":"query","required":false,"description":"A Dockerfile's path, said outright: the compose file is ignored and the service's setting changes to this.","schema":{"type":"string"}},{"name":"context","in":"query","required":false,"description":"The directory to build in (default: the Dockerfile's).","schema":{"type":"string"}},{"name":"service","in":"query","required":false,"description":"A service by name or id. With a compose file: only that one of it. With a Dockerfile: the service to deploy to; a new name creates it.","schema":{"type":"string"}},{"name":"profiles","in":"query","required":false,"description":"With a compose file: the profiles to deploy, comma-separated (`production,init`). Its services in other profiles are left out, as compose does; without this, every service that has profiles is.","schema":{"type":"string"}},{"name":"dir","in":"query","required":false,"description":"The directory's name: what a new service from a Dockerfile is called when `service` isn't given.","schema":{"type":"string"}},{"name":"message","in":"query","required":false,"description":"Shown with the deployment (a commit's subject line).","schema":{"type":"string"}},{"name":"commit","in":"query","required":false,"description":"The commit the code is from, for the history.","schema":{"type":"string"}},{"name":"branch","in":"query","required":false,"description":"The branch it's from.","schema":{"type":"string"}},{"name":"hold","in":"query","required":false,"description":"`1`: start new versions of existing services as candidates, to be promoted.","schema":{"type":"string"}},{"name":"staged","in":"query","required":false,"description":"`keep`: leave staged variable and secret changes staged. Default `apply`.","schema":{"type":"string"}},{"name":"group","in":"query","required":false,"description":"Show newly created services as one group of this name.","schema":{"type":"string"}},{"name":"config_wins","in":"query","required":false,"description":"`1`: settings changed in the dashboard since alf.toml set them go back to the file's (otherwise the dashboard's stay).","schema":{"type":"string"}},{"name":"format","in":"query","required":false,"description":"`text`: lines of key=value, then `---` and text to show (what the deploy tool reads). Default JSON.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"text/plain":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/push/{ticket}":{"post":{"tags":["Pushing code"],"summary":"Start a push with a one-time link","description":"The same as starting a push, with a link from the dashboard or an agent in place of a token. The link works once.","operationId":"post_push_ticket","parameters":[{"name":"ticket","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"text/plain":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/push/preview":{"delete":{"tags":["Pushing code"],"summary":"End a preview by its name","description":"What CI calls when a pull request closes. Authorization as for starting a push.","operationId":"delete_push_preview","parameters":[{"name":"project","in":"query","required":false,"description":"The project the preview is of.","schema":{"type":"string"}},{"name":"preview","in":"query","required":false,"description":"The preview's name.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/pushes/{secret}/parts/{n}":{"put":{"tags":["Pushing code"],"summary":"Send a part of the code","description":"The code is one gzipped tar, cut into parts of `part_size` bytes (the last one smaller), numbered from 1. Parts can be sent again; `max_size` from the start of the push is the limit for all of them together.","operationId":"put_pushes_secret_parts_n","parameters":[{"name":"secret","in":"path","required":true,"schema":{"type":"string"}},{"name":"n","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/pushes/{secret}/files":{"post":{"tags":["Pushing code"],"summary":"Say the code's files","description":"Once a push has started, the code's files with their fingerprints (`{ \"manifest\": { \"<path>\": \"<sha256>:<f|x|l>\" } }`, paths from the code's root): the answer says whether to send them all (`full`), only `files` (`changes`: what changed since these services' last push), or nothing (`same`). The start's `builds` say what each build can see (its context, less its .dockerignore), so only that needs listing and sending.","operationId":"post_pushes_secret_files","parameters":[{"name":"secret","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/pushes/{secret}/complete":{"post":{"tags":["Pushing code"],"summary":"Complete a push","description":"Puts the parts together and carries the plan out: creates what's new and starts the deployments. Answers like the push's status. With alf.toml, a JSON body may bring values for the secrets it asks for: `{ \"secrets\": { \"web\": { \"STRIPE_KEY\": \"…\" } } }`, and the conflicts (from the start) whose file value should win: `{ \"use_file\": [\"services.web.memory_mb\"] }`; the others keep the dashboard's.","operationId":"post_pushes_secret_complete","parameters":[{"name":"secret","in":"path","required":true,"schema":{"type":"string"}},{"name":"parts","in":"query","required":false,"description":"How many parts were sent.","schema":{"type":"string"}}],"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/pushes/{secret}":{"get":{"tags":["Pushing code"],"summary":"How a push went","description":"Each service it touched with its deployment's state; on failure, why (`failure`, with the container's last output).","operationId":"get_pushes_secret","parameters":[{"name":"secret","in":"path","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","required":false,"description":"Seconds (up to 25) to hold the answer until the deployments are done.","schema":{"type":"string"}},{"name":"format","in":"query","required":false,"description":"`text` for the deploy tool.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/projects/{project_id}/push-ticket":{"post":{"tags":["Pushing code"],"summary":"Get a link to push code to a project","description":"Returns a one-time link and the shell command that deploys the current directory with it. `staged` and `hold` say how the deployments behave.","operationId":"post_projects_project_id_push_ticket","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"staged":{"type":"string","enum":["apply","keep"]},"hold":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/push-ticket":{"post":{"tags":["Pushing code"],"summary":"Get a link to push code to one service","description":"Like a project's link, for one service: the code is deployed to it, built from its Dockerfile. `staged_config` in the answer says what happens to staged variables.","operationId":"post_services_service_id_push_ticket","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"staged":{"type":"string","enum":["apply","keep"]},"hold":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/domains":{"get":{"tags":["Domains"],"summary":"List a service's domains","operationId":"get_services_service_id_domains","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Domains"],"summary":"Add a domain","description":"Returns the DNS record to create at the domain's provider; the certificate follows by itself.","operationId":"post_services_service_id_domains","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hostname":{"type":"string","minLength":1,"maxLength":300}},"required":["hostname"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/domains/{domain_id}/check":{"post":{"tags":["Domains"],"summary":"Check a domain now","description":"Its status and what its DNS says.","operationId":"post_domains_domain_id_check","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string"},"description":"The domain's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/domains/{domain_id}":{"delete":{"tags":["Domains"],"summary":"Remove a domain","operationId":"delete_domains_domain_id","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string"},"description":"The domain's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/my-ip":{"get":{"tags":["Public ports"],"summary":"The address this request comes from","description":"What to put on an allowlist for \"just me\".","operationId":"get_my_ip","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/ports":{"get":{"tags":["Public ports"],"summary":"List a service's public ports","description":"With the host name to connect to, the servers behind it, and the addresses turned away lately.","operationId":"get_services_service_id_ports","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Public ports"],"summary":"Open a public port","description":"The platform picks the public port (20000 to 29999). `allow` is required: IP addresses or ranges, or `[\"anywhere\"]`.","operationId":"post_services_service_id_ports","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"protocol":{"type":"string","enum":["tcp","udp"],"default":"tcp"},"target_port":{"type":"integer","minimum":1,"maximum":65535},"allow":{"type":"array","items":{"type":"string","minLength":1,"maxLength":60},"minItems":1,"maxItems":50}},"required":["allow"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/ports/{port_id}":{"patch":{"tags":["Public ports"],"summary":"Change who may connect","operationId":"patch_ports_port_id","parameters":[{"name":"port_id","in":"path","required":true,"schema":{"type":"string"},"description":"The port's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target_port":{"type":"integer","minimum":1,"maximum":65535},"allow":{"type":"array","items":{"type":"string","minLength":1,"maxLength":60},"minItems":1,"maxItems":50}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Public ports"],"summary":"Close a public port","operationId":"delete_ports_port_id","parameters":[{"name":"port_id","in":"path","required":true,"schema":{"type":"string"},"description":"The port's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/ports/{port_id}/check":{"post":{"tags":["Public ports"],"summary":"Test a port from outside","description":"TCP only: reachable, refused by the server, or blocked by a firewall on the way.","operationId":"post_ports_port_id_check","parameters":[{"name":"port_id","in":"path","required":true,"schema":{"type":"string"},"description":"The port's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/canvas":{"put":{"tags":["Groups"],"summary":"Move cards on the canvas","description":"Where cards were moved to, by their service's or bucket's id. Each is saved on its own and is the same for everyone; the project's `canvas` has them all.","operationId":"put_projects_project_id_canvas","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"positions":{"type":"object","additionalProperties":{"type":"object","properties":{"x":{"type":"number","minimum":-1000000,"maximum":1000000},"y":{"type":"number","minimum":-1000000,"maximum":1000000}},"required":["x","y"]}}},"required":["positions"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/groups":{"get":{"tags":["Groups"],"summary":"List a project's groups","operationId":"get_projects_project_id_groups","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Groups"],"summary":"Create a group","description":"With at least one service, computer or bucket of the project in it.","operationId":"post_projects_project_id_groups","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"color":{"type":"string","enum":["indigo","emerald","amber","rose","sky","violet","slate"]},"service_ids":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":200,"default":[]},"bucket_ids":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":200,"default":[]}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/groups/{group_id}":{"patch":{"tags":["Groups"],"summary":"Change a group","description":"`service_ids` and `bucket_ids` each set the members of their kind as a whole (computers are services); a kind left out isn't changed. A group left empty is removed (`group` is then null).","operationId":"patch_groups_group_id","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string"},"description":"The group's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"color":{"type":"string","enum":["indigo","emerald","amber","rose","sky","violet","slate"]},"service_ids":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":200},"bucket_ids":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":200}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Groups"],"summary":"Remove a group","description":"What was in it stays, ungrouped.","operationId":"delete_groups_group_id","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string"},"description":"The group's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/templates":{"get":{"tags":["One-click deploys"],"summary":"List the one-click deploys","description":"No account needed. Read from the public repository about once a minute.","operationId":"get_templates","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/templates/{slug}":{"get":{"tags":["One-click deploys"],"summary":"Get a one-click deploy","description":"With its alf.toml (`file`). No account needed.","operationId":"get_templates_slug","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/projects/{project_id}/templates/{slug}/deploy":{"post":{"tags":["One-click deploys"],"summary":"Deploy one into a project","description":"Creates its services, databases and buckets as one group. Generated passwords and keys come back in `generated` (by `service.NAME`), this once.","operationId":"post_projects_project_id_templates_slug_deploy","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"variables":{"type":"object","additionalProperties":{"type":"string","maxLength":4000}}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/usage":{"get":{"tags":["Usage"],"summary":"An organization's usage","operationId":"get_organizations_organization_id_usage","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"month","in":"query","required":false,"description":"The month as YYYY-MM (default: this one).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/pricing":{"get":{"tags":["Usage"],"summary":"The price list","description":"What Alf costs, as numbers: the sizes a service runs at (memory and cores, each with its price for a month awake; billed by the hour awake), per GB of a volume's size and per GB stored a month; the plans (each charged every month once the free credit is used up, covering that much usage, with the largest size each allows) and the free credit (cents). No sign-in needed.","operationId":"get_pricing","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}}},"security":[]}},"/api/v1/organizations/{organization_id}/billing":{"get":{"tags":["Usage"],"summary":"Billing","description":"Where the organization stands (`standing`: trial, paying, needs_card (no payment method yet: one has to be added before anything is created or deployed, the free credit comes after), unpaid, suspended, exempt, or preview while nothing is charged yet) its `plan` (hobby or business: its monthly price, which covers that much usage, its limits and its support) and its `phase` (free_trial, paying, not_billed); what's left of its free credit; `month`: what it would be charged if the month ended now, line by line; `services`: each service's size, hours awake and cost so far, with where its month is heading; `forecast`: the whole month at this rate (a service that never sleeps is awake to the end of it, one that sleeps is awake as much as over its last days, each at its size now; volumes keep their size and what's stored stays stored); and its invoices.","operationId":"get_organizations_organization_id_billing","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/billing/standing":{"get":{"tags":["Usage"],"summary":"Billing, in short","description":"Whether the organization can create and deploy: its `standing` (`needs_card` means a payment method has to be added first, which comes before anything is created; `unpaid`, `suspended`), its payment method and what's left of its free credit.","operationId":"get_organizations_organization_id_billing_standing","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/billing/plan":{"put":{"tags":["Usage"],"summary":"Choose the plan","description":"Hobby or Business. A plan's monthly price covers that much usage; what's used beyond it is billed on top. Business needs a payment method once billing is on; Hobby is refused while a service or volume is bigger than it allows. Admins only.","operationId":"put_organizations_organization_id_billing_plan","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["hobby","business"]}},"required":["plan"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/billing/cap":{"put":{"tags":["Usage"],"summary":"Set a limit on the month","description":"Admins. What this month may cost at most, in cents; null takes the limit away. When it's reached, creating and deploying stop the way an unpaid invoice stops them, and `standing` becomes `capped` — but what already runs keeps running, because turning apps off over money that was agreed is a person's decision. Worth setting wherever an agent can deploy: it's the ceiling that makes handing one the keys safe.","operationId":"put_organizations_organization_id_billing_cap","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cap_cents":{"type":["integer","null"],"minimum":0,"maximum":10000000}},"required":["cap_cents"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/billing/payment-method":{"post":{"tags":["Usage"],"summary":"Add a payment method","description":"Answers with the address of a page at the payment provider where the payment method is added or replaced. `next`: the page of the dashboard to come back to (with `card=added`, or `card=cancelled`); Billing if not given.","operationId":"post_organizations_organization_id_billing_payment_method","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"next":{"type":"string","maxLength":300},"start_now":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/previews":{"get":{"tags":["Previews"],"summary":"List a project's previews","operationId":"get_projects_project_id_previews","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Previews"],"summary":"Create a preview","description":"A copy of the project with its own addresses. With a `name`, pushes find it again (`preview` when starting a push). It ends by itself after `ttl_hours`.","operationId":"post_projects_project_id_previews","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":["string","null"],"maxLength":60},"ttl_hours":{"type":"integer","minimum":1,"maximum":168}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/extend":{"post":{"tags":["Previews"],"summary":"Keep a preview longer","description":"The path's project is the preview itself.","operationId":"post_projects_project_id_extend","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hours":{"type":"integer","minimum":1,"maximum":168}},"required":["hours"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/volumes/{volume_id}/snapshots":{"get":{"tags":["Snapshots"],"summary":"List a volume's snapshots","description":"Newest first, with the volume's schedule and the server they're kept on.","operationId":"get_volumes_volume_id_snapshots","parameters":[{"name":"volume_id","in":"path","required":true,"schema":{"type":"string"},"description":"The volume's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Snapshots"],"summary":"Take a snapshot","description":"Starts a snapshot on the volume's server and answers at once (`status: creating`); list the snapshots to see it turn `ready`. The service's container is frozen while its files are copied, unless `freeze` is false.","operationId":"post_volumes_volume_id_snapshots","parameters":[{"name":"volume_id","in":"path","required":true,"schema":{"type":"string"},"description":"The volume's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":["string","null"],"maxLength":200},"freeze":{"type":"boolean"},"offsite":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/volumes/{volume_id}/snapshot-schedule":{"put":{"tags":["Snapshots"],"summary":"Set a volume's snapshot schedule","description":"A snapshot every `every_h` hours (null: none), keeping the newest `keep` scheduled ones (at once: a smaller count, or the schedule switched off, deletes the older ones now); `offsite` gives every new snapshot a copy off the server. Fields left out stay as they are.","operationId":"put_volumes_volume_id_snapshot_schedule","parameters":[{"name":"volume_id","in":"path","required":true,"schema":{"type":"string"},"description":"The volume's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"every_h":{"type":["integer","null"]},"keep":{"type":"integer","minimum":1,"maximum":60},"offsite":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/snapshots/{snapshot_id}/restore":{"post":{"tags":["Snapshots"],"summary":"Restore a snapshot","description":"Replaces what's in the volume with the snapshot's files and restarts the service on them. Runs in the background: the snapshot's `restore.state` goes from `running` to `done` or `failed`.","operationId":"post_snapshots_snapshot_id_restore","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string"},"description":"The snapshot's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/snapshots/{snapshot_id}/download":{"post":{"tags":["Snapshots"],"summary":"Get a download link","description":"A link to the snapshot's archive (a gzipped tar of the volume's files), good for ten minutes.","operationId":"post_snapshots_snapshot_id_download","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string"},"description":"The snapshot's id."}],"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/snapshots/{snapshot_id}/offsite":{"post":{"tags":["Snapshots"],"summary":"Copy a snapshot off the server","description":"Has the server send the snapshot's archive to the platform's storage, where it survives the server. The snapshot's `offsite.state` goes from `uploading` to `stored`. It counts as storage.","operationId":"post_snapshots_snapshot_id_offsite","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string"},"description":"The snapshot's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/snapshots/{snapshot_id}/keep":{"post":{"tags":["Snapshots"],"summary":"Keep an agent's snapshot","description":"A snapshot a coding agent took goes by itself after a week (its `expires_at`). Kept, it stays like one taken by hand: a volume keeps the newest ten of those, so the oldest goes when there'd be more. People only: an agent's token can't keep its own.","operationId":"post_snapshots_snapshot_id_keep","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string"},"description":"The snapshot's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/snapshots/{snapshot_id}":{"delete":{"tags":["Snapshots"],"summary":"Delete a snapshot","description":"Its file on the server and its copy off the server go too.","operationId":"delete_snapshots_snapshot_id","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string"},"description":"The snapshot's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/snapshot-downloads/{token}":{"get":{"tags":["Snapshots"],"summary":"Download a snapshot","description":"The archive itself. The link is the permission: no token.","operationId":"get_snapshot_downloads_token","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/gzip":{"schema":{"type":"string"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[]}},"/api/v1/services/{service_id}/sessions":{"post":{"tags":["Services"],"summary":"Open a session in a container","description":"Admins. Opens a container to work in and keeps it, so what one command does is still there for the next. By default it's a fresh container of the service's image with its variables, secrets, volumes and project network; `live` works in the service's own running container instead, which is held awake while the session lasts. It closes itself after `idle_s` without a command. A fresh session's container is metered like any other container of that service.","operationId":"post_services_service_id_sessions","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"live":{"type":"boolean"},"idle_s":{"type":"integer","minimum":60,"maximum":7200},"server_id":{"type":"string"},"container":{"type":"integer","minimum":1,"maximum":1000}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"get":{"tags":["Services"],"summary":"Sessions open on a service","operationId":"get_services_service_id_sessions","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/sessions/{session_id}":{"delete":{"tags":["Services"],"summary":"Close a session","description":"Admins. Removes the container that was being worked in; a live session leaves the service's own container alone.","operationId":"delete_sessions_session_id","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string"},"description":"The session's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/sessions/{session_id}/commands":{"get":{"tags":["Services"],"summary":"What was run in a session","operationId":"get_sessions_session_id_commands","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string"},"description":"The session's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/sessions/{session_id}/terminal":{"post":{"tags":["Services"],"summary":"A terminal in a session","description":"Admins. Answers with a WebSocket address, good for a few minutes, that opens a terminal in the session: what's typed goes in and what's printed comes back as it appears. The messages are JSON — {\"i\": base64} to type, {\"r\": [rows, cols]} to resize, {\"sig\": \"INT\"} for Ctrl-C; back come {\"o\"} and {\"e\"} for output and {\"x\"} for the exit code. For agents, exec is the better tool: a terminal can be recorded but not meaningfully read back command by command.","operationId":"post_sessions_session_id_terminal","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string"},"description":"The session's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/exec":{"post":{"tags":["Services"],"summary":"Run a command","description":"Admins. With `session_id` it runs in that session, so it sees what earlier commands left behind; without one it runs in a container of its own (give `service_id`) that is thrown away afterwards (`session_id` null in the answer). A string runs with `sh -c`; an array runs as it is. Answers with the exit code, stdout and stderr once it's done; long output keeps its end.","operationId":"post_exec","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"command":{"anyOf":[{"type":"string","minLength":1,"maxLength":8000},{"type":"array","items":{"type":"string","maxLength":8000},"minItems":1,"maxItems":200}]},"session_id":{"type":"string"},"service_id":{"type":"string"},"live":{"type":"boolean"},"timeout_s":{"type":"integer","minimum":1,"maximum":21600},"workdir":{"type":"string","maxLength":1024},"user":{"type":"string","maxLength":200},"env":{"type":"object","additionalProperties":{"type":"string","maxLength":20000}},"server_id":{"type":"string"}},"required":["command"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/environments":{"post":{"tags":["Projects"],"summary":"Make an environment","description":"Admins. Copies the project into an environment that doesn't expire — staging beside production — with the same services, settings, variables and secrets, its own addresses, and volumes that start empty. A preview is the same copy with an expiry; an environment is one meant to stay.","operationId":"post_projects_project_id_environments","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"get":{"tags":["Projects"],"summary":"A project's environments","operationId":"get_projects_project_id_environments","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/releases":{"post":{"tags":["Projects"],"summary":"Record what an environment is running","description":"Admins. Writes down the version every service here is running, under a name, so the same versions can be put somewhere else later.","operationId":"post_projects_project_id_releases","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"note":{"type":["string","null"],"maxLength":500}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"get":{"tags":["Projects"],"summary":"Releases cut here","operationId":"get_projects_project_id_releases","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/promotions":{"get":{"tags":["Projects"],"summary":"What has been promoted into this environment","operationId":"get_projects_project_id_promotions","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/releases/{release_id}":{"get":{"tags":["Projects"],"summary":"A release, and where it can go","description":"What each service was running, and the environments it can be promoted into.","operationId":"get_releases_release_id","parameters":[{"name":"release_id","in":"path","required":true,"schema":{"type":"string"},"description":"The release's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Projects"],"summary":"Forget a release","description":"Admins. Throws the note away; whatever was promoted from it carries on running.","operationId":"delete_releases_release_id","parameters":[{"name":"release_id","in":"path","required":true,"schema":{"type":"string"},"description":"The release's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/releases/{release_id}/promote":{"post":{"tags":["Projects"],"summary":"Promote a release","description":"Admins. Deploys the versions this release recorded into another environment. Only the versions move: that environment's variables, secrets, domains and volumes are its own and aren't touched. Refused unless every service in the release exists there, so a promotion can't half happen and leave a mixture running. A service built from pushed code is rebuilt from the same commit instead of running the same image, because an image belongs to the service that built it; the answer says which.","operationId":"post_releases_release_id_promote","parameters":[{"name":"release_id","in":"path","required":true,"schema":{"type":"string"},"description":"The release's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"into_project_id":{"type":"string"}},"required":["into_project_id"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/logs/search":{"get":{"tags":["Services"],"summary":"Search what a service wrote","description":"What the service has written over the last few days, not only what its container still holds — so a crash this morning, or what a replaced container said, is still there. `q` is text the line must contain, `from` and `to` are RFC 3339 times, `stderr_only` narrows it to what went to stderr, `server_id` (and with it `container`, 1 its own, 2 and up one added under load) to one server or container. Each line says its `server_id` and `container`. Answers oldest first, as a log reads, with `more` set when it was cut to the limit. Use the service's logs for what it's saying right now.","operationId":"get_services_service_id_logs_search","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/logs/span":{"get":{"tags":["Services"],"summary":"How far back a service's log goes","description":"The oldest and newest lines kept, and how many — worth a look before asking for a time that may no longer be kept.","operationId":"get_services_service_id_logs_span","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/errors":{"get":{"tags":["Services"],"summary":"A service's errors, grouped","description":"From its kept log (the last `hours`, 24 by default, three days at most): each error with its stack trace in one piece, the same error counted once — numbers, ids and quoted values taken out — with how many times, and when first and last. Most recent first.","operationId":"get_services_service_id_errors","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/scheduled":{"post":{"tags":["Services"],"summary":"Run a command on a schedule","description":"Admins. A command run over and over in a container of the service: a nightly clean-up, an hourly digest. `schedule` is five fields in UTC (minute hour day-of-month month day-of-week) or a shorthand like `@daily`. Each run is what a one-off exec does — the service's image, variables, secrets, volumes and project network, in a container thrown away afterwards. A run still going when the next is due is skipped rather than started twice.","operationId":"post_services_service_id_scheduled","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"schedule":{"type":"string","minLength":1,"maxLength":100},"command":{"anyOf":[{"type":"string","minLength":1,"maxLength":8000},{"type":"array","items":{"type":"string","maxLength":8000},"minItems":1,"maxItems":200}]},"timeout_s":{"type":"integer","minimum":1,"maximum":21600},"enabled":{"type":"boolean"}},"required":["name","schedule","command"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"get":{"tags":["Services"],"summary":"A service's scheduled commands","description":"When each next runs, and how the last run went.","operationId":"get_services_service_id_scheduled","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/scheduled/{job_id}":{"patch":{"tags":["Services"],"summary":"Change a scheduled command","description":"Admins. `enabled: false` switches it off without deleting it.","operationId":"patch_scheduled_job_id","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"},"description":"The job's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"schedule":{"type":"string","minLength":1,"maxLength":100},"command":{"anyOf":[{"type":"string","minLength":1,"maxLength":8000},{"type":"array","items":{"type":"string","maxLength":8000},"minItems":1,"maxItems":200}]},"timeout_s":{"type":"integer","minimum":1,"maximum":21600},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Services"],"summary":"Delete a scheduled command","operationId":"delete_scheduled_job_id","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"},"description":"The job's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/scheduled/{job_id}/run":{"post":{"tags":["Services"],"summary":"Run a scheduled command now","description":"Admins. Without waiting for its time; answers with how it went.","operationId":"post_scheduled_job_id_run","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"},"description":"The job's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/notification-channels":{"get":{"tags":["Notifications"],"summary":"List notification channels","operationId":"get_organizations_organization_id_notification_channels","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Notifications"],"summary":"Add a notification channel","description":"`target` is the webhook URL or the email address.","operationId":"post_organizations_organization_id_notification_channels","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["slack","discord","webhook","email"]},"name":{"type":"string","maxLength":60},"target":{"type":"string","minLength":3,"maxLength":500},"events":{"type":"array","items":{"type":"string","enum":["deploy_failed","deploy_live","agent_change","approval_needed","server_offline","backup_failed","spend_capped","volume_full","volume_grew","memory_pressure","alert_firing","alert_resolved","service_moved"]},"minItems":1}},"required":["kind","target","events"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/notification-channels/{channel_id}/test":{"post":{"tags":["Notifications"],"summary":"Send a test notification","operationId":"post_notification_channels_channel_id_test","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string"},"description":"The channel's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/notification-channels/{channel_id}":{"delete":{"tags":["Notifications"],"summary":"Remove a notification channel","operationId":"delete_notification_channels_channel_id","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string"},"description":"The channel's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/alerts":{"get":{"tags":["Notifications"],"summary":"What you're told about","description":"The alert rules, the measures a rule can watch, and which of them are firing right now and since when.","operationId":"get_organizations_organization_id_alerts","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Notifications"],"summary":"Watch a measure","description":"Fires when a service stays past the threshold for the whole window, and sends the all-clear when it comes back. Leave out `service_id` to watch every service, including ones created later. An error rate or a response time is only judged when there were enough requests to mean anything.","operationId":"post_organizations_organization_id_alerts","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"service_id":{"type":["string","null"],"maxLength":100},"measure":{"type":"string","enum":["error_rate","p95_ms","memory_percent","failing"]},"threshold":{"type":"number","minimum":0,"maximum":1000000},"window_minutes":{"type":"integer","minimum":1,"maximum":60}},"required":["measure","threshold"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/alerts/watch":{"post":{"tags":["Notifications"],"summary":"Watch the usual three","description":"Creates the rules almost everyone wants — failed requests, slow responses, and the service not running — for one service or for all of them. Rules that already exist are left alone.","operationId":"post_organizations_organization_id_alerts_watch","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"service_id":{"type":["string","null"],"maxLength":100}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/alerts/{alert_id}":{"patch":{"tags":["Notifications"],"summary":"Change an alert","description":"Its threshold, its window, or whether it's on. Turning it off stops anything it is firing, so nobody is left with an alert they can't resolve.","operationId":"patch_organizations_organization_id_alerts_alert_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"alert_id","in":"path","required":true,"schema":{"type":"string"},"description":"The alert's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"threshold":{"type":"number","minimum":0,"maximum":1000000},"window_minutes":{"type":"integer","minimum":1,"maximum":60},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Notifications"],"summary":"Delete an alert","operationId":"delete_organizations_organization_id_alerts_alert_id","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."},{"name":"alert_id","in":"path","required":true,"schema":{"type":"string"},"description":"The alert's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/computers":{"get":{"tags":["Computers"],"summary":"List computers","description":"Every computer of the organization, whichever project it is in.","operationId":"get_organizations_organization_id_computers","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Computers"],"summary":"Make a computer","description":"In the project `project_id` names, or a new project of its own when it is left out. `system`: `claude` (the default) is Debian 12 with Node 22, which the dashboard gives Claude Code and Alf's tools once it runs (`setup` says it is still to do); `debian`, `ubuntu` and `fedora` are the stock systems and nothing else. It runs until it is stopped, so what is left working in it carries on, and it is kept until it is deleted unless `ttl_hours` gives it a time to go.","operationId":"post_organizations_organization_id_computers","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":["string","null"],"maxLength":60},"system":{"type":"string","enum":["claude","debian","ubuntu","fedora"]},"image":{"type":["string","null"],"maxLength":300},"size":{"type":"string","enum":["xs","s","m","l","xl","2xl","3xl","4xl","5xl"]},"memory_mb":{"type":"integer","minimum":256,"maximum":16384},"cpus":{"type":["number","null"],"minimum":0.25,"maximum":32},"disk_gb":{"type":"integer","minimum":1,"maximum":100},"ttl_hours":{"type":["integer","null"],"minimum":1,"maximum":8760},"idle_minutes":{"type":["integer","null"],"minimum":5,"maximum":1440},"regions":{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20},"project_id":{"type":["string","null"],"maxLength":100}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/computers":{"post":{"tags":["Computers"],"summary":"Make a computer in a project","description":"As making one for the organization, in this project.","operationId":"post_projects_project_id_computers","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":["string","null"],"maxLength":60},"system":{"type":"string","enum":["claude","debian","ubuntu","fedora"]},"image":{"type":["string","null"],"maxLength":300},"size":{"type":"string","enum":["xs","s","m","l","xl","2xl","3xl","4xl","5xl"]},"memory_mb":{"type":"integer","minimum":256,"maximum":16384},"cpus":{"type":["number","null"],"minimum":0.25,"maximum":32},"disk_gb":{"type":"integer","minimum":1,"maximum":100},"ttl_hours":{"type":["integer","null"],"minimum":1,"maximum":8760},"idle_minutes":{"type":["integer","null"],"minimum":5,"maximum":1440},"regions":{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}":{"get":{"tags":["Computers"],"summary":"Get a computer","operationId":"get_computers_computer_id","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Computers"],"summary":"Delete a computer","description":"It and its disk go; its project stays. It can't be undone.","operationId":"delete_computers_computer_id","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/extend":{"post":{"tags":["Computers"],"summary":"Say how long a computer is kept","description":"This long from now, up to a year; `ttl_hours` null keeps it until somebody deletes it.","operationId":"post_computers_computer_id_extend","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ttl_hours":{"type":["integer","null"],"minimum":1,"maximum":8760}},"required":["ttl_hours"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/idle":{"post":{"tags":["Computers"],"summary":"Switch it off when nobody uses it","description":"After `idle_minutes` with no shell open in it; null: it runs until it is stopped.","operationId":"post_computers_computer_id_idle","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idle_minutes":{"type":["integer","null"],"minimum":5,"maximum":1440}},"required":["idle_minutes"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/stop":{"post":{"tags":["Computers"],"summary":"Stop a computer","description":"Whatever is running in it ends; its container and disk are kept. Nothing is billed while it is stopped.","operationId":"post_computers_computer_id_stop","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/start":{"post":{"tags":["Computers"],"summary":"Start a computer","description":"With everything that was on its disk.","operationId":"post_computers_computer_id_start","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/restart":{"post":{"tags":["Computers"],"summary":"Restart a computer","description":"Whatever is running in it ends, and it starts again with its variables and secrets as they are now, and any change that was waiting for a restart (`restart_pending`). Its disk is kept. `restarting` is true until its server has stopped it; then it starts. A stopped computer is started.","operationId":"post_computers_computer_id_restart","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/agents":{"get":{"tags":["Computers"],"summary":"The coding agents it can have, and which it has","operationId":"get_computers_computer_id_agents","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Computers"],"summary":"Install a coding agent in it","description":"Installs it into the computer's own disk (~/.local/bin, under /workspace), so it survives the container being replaced: the whole program downloaded and checked first, then put in place in one step, so an install that's cut off leaves the old one working. What the stock systems lack for it (curl, bash, tar) comes from their own package manager first. It counts as installed once `--version` runs. Returns what the command printed; it takes a minute or two.","operationId":"post_computers_computer_id_agents","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"type":"string","enum":["claude","codex","opencode"]}},"required":["agent"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/mcp":{"post":{"tags":["Computers"],"summary":"Give an agent in it Alf's tools","description":"Makes an agent token for one project (or the whole organization) and configures the agent to use Alf's MCP endpoint with it. The token reaches the agent as an environment variable and is never written into a config file. For Claude Code, it completes a claude computer's `setup`. Needs an admin.","operationId":"post_computers_computer_id_mcp","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"type":"string","enum":["claude","codex","opencode"]},"project_id":{"type":["string","null"],"maxLength":100},"scope":{"type":"string","enum":["read","deploy"]},"can_run":{"type":"boolean"}},"required":["agent"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/setup/skip":{"post":{"tags":["Computers"],"summary":"Leave a claude computer's setup be","description":"`setup` becomes null: nothing installs Claude Code in it by itself.","operationId":"post_computers_computer_id_setup_skip","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/agent-key":{"put":{"tags":["Computers"],"summary":"Keep an agent's provider key in it","description":"Stored as a secret of the computer, so the agent finds it in its environment. An empty value takes it away.","operationId":"put_computers_computer_id_agent_key","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"type":"string","enum":["claude","codex","opencode"]},"value":{"type":"string","maxLength":500}},"required":["agent","value"]}}}},"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/connect":{"post":{"tags":["Computers"],"summary":"A command for a shell in it, from your own terminal","description":"Opens a session in the computer's own container and returns one line to paste into a terminal: `curl -fsSL https://runalf.com/connect.sh | sh -s -- '<link>'`. The link is good for a few minutes and for that session only. Needs python3 on the machine it's run on. Needs an admin.","operationId":"post_computers_computer_id_connect","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/expose":{"post":{"tags":["Computers"],"summary":"Put a computer on the web, or take it off","description":"Gives it the usual address on the port you name, so something you started in its shell can be looked at from outside; a custom domain is then added as it is for any service. `port` null takes the address away. Nothing in the computer is restarted.","operationId":"post_computers_computer_id_expose","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port":{"type":["integer","null"],"minimum":1,"maximum":65535}},"required":["port"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/ports":{"post":{"tags":["Computers"],"summary":"Share one of a computer's ports, or stop sharing it","description":"A shared port gets an address of its own, `https://{computer's address}--{port}.smartalfred.com`, that goes to that port inside the computer — a server listening only on localhost included. Anyone with the address can reach it. The same as `alf share 3000` in the computer's terminal. Nothing in the computer is restarted.","operationId":"post_computers_computer_id_ports","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port":{"type":"integer","minimum":1,"maximum":65535},"shared":{"type":"boolean"}},"required":["port","shared"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/share-ports":{"post":{"tags":["Computers"],"summary":"Share every port something listens on in a computer","description":"While on, each port a process starts listening on is shared as it appears, except debuggers', databases' and SSH's (9229, 9222, 5432, 3306, 6379, 27017, 22), which are only ever shared by name. Off by default.","operationId":"post_computers_computer_id_share_ports","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/startup":{"put":{"tags":["Computers"],"summary":"Set what a computer starts when it starts","description":"Each is a shell command run as its user (dev) in /workspace, started at once and whenever the computer starts, and started again whenever it ends; what it prints is in the computer's logs. The list given replaces the one it had; an empty list takes them all away. Nothing restarts. `machine.startup` says how each is doing.","operationId":"put_computers_computer_id_startup","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40},"command":{"type":"string","maxLength":4000}},"required":["name","command"]},"maxItems":20}},"required":["commands"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/docker":{"post":{"tags":["Computers"],"summary":"Run Docker in a computer, or not","description":"docker and docker compose in the computer. Its container is made with what Docker needs, so this waits for a restart (`restart_pending`); Docker is installed the first time it starts with it. `machine.docker` says how it is.","operationId":"post_computers_computer_id_docker","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/resize":{"post":{"tags":["Computers"],"summary":"Change a computer's size","description":"Its `size` (up to 3xl), or said as memory and cores: the smallest size with at least so much. Given to it as it runs: nothing restarts.","operationId":"post_computers_computer_id_resize","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"size":{"type":"string","enum":["xs","s","m","l","xl","2xl","3xl","4xl","5xl"]},"memory_mb":{"type":"integer","minimum":256,"maximum":16384},"cpus":{"type":["number","null"],"minimum":0.25,"maximum":32}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/computers/{computer_id}/duplicate":{"post":{"tags":["Computers"],"summary":"Make another computer like this one","description":"In the same project, on the same server: its system, everything installed in it and everything in /workspace as they are now, its size, Docker and startup commands, and its variables (not its secrets). The computer must be running. Returns the new computer, which starts as a copy.","operationId":"post_computers_computer_id_duplicate","parameters":[{"name":"computer_id","in":"path","required":true,"schema":{"type":"string"},"description":"The computer's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":["string","null"],"maxLength":60}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/devices":{"get":{"tags":["Devices"],"summary":"A project's devices","description":"The ones working now, with who joined them, which of the project's servers they're connected to and from where. `?all=1` adds revoked and expired ones.","operationId":"get_projects_project_id_devices","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Devices"],"summary":"Join a device to a project's network","description":"With its WireGuard public key. Returns the device and what it needs to connect: its address, the project's servers as peers (each with the project's subnet on it) and the project's services with their addresses and ports. `days` (default 30, null for good) is how long it works. Needs an admin.","operationId":"post_projects_project_id_devices","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"public_key":{"type":"string","maxLength":60},"days":{"type":["integer","null"],"minimum":1,"maximum":365}},"required":["name","public_key"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/devices":{"get":{"tags":["Devices"],"summary":"Every device working now","description":"In all of the organization's projects.","operationId":"get_organizations_organization_id_devices","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/devices/{device_id}":{"get":{"tags":["Devices"],"summary":"Get a device","operationId":"get_devices_device_id","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string"},"description":"The device's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"patch":{"tags":["Devices"],"summary":"Rename a device, or change how long it works","description":"`days` from now, or null for good.","operationId":"patch_devices_device_id","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string"},"description":"The device's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":["integer","null"],"minimum":1,"maximum":365},"name":{"type":"string","minLength":1,"maxLength":60}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Devices"],"summary":"Revoke a device","description":"Every server drops it at once. It stays in the list as revoked. Its owner's or an admin's to do.","operationId":"delete_devices_device_id","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string"},"description":"The device's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/devices/{device_id}/config":{"get":{"tags":["Devices"],"summary":"What a device needs to connect now","description":"Its address, the project's servers as peers and its services, as they are now. Its owner's or an admin's to ask for.","operationId":"get_devices_device_id_config","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string"},"description":"The device's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/databases":{"get":{"tags":["Databases"],"summary":"The databases a project can have in one click","operationId":"get_databases","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/databases":{"post":{"tags":["Databases"],"summary":"Add a database to a project","description":"Postgres, MySQL or Redis, on the engine's official image: a generated password, a disk of its own, private and always running, and a snapshot every day (kept a week, and off the server where that's set up). Its connection URL is its DATABASE_URL (REDIS_URL for Redis) secret; other services read it as ${{postgres.DATABASE_URL}}.","operationId":"post_projects_project_id_databases","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"engine":{"type":"string","enum":["postgres","mysql","redis","mongodb"]},"name":{"type":"string","maxLength":60},"regions":{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20},"size":{"type":"string","enum":["xs","s","m","l","xl","2xl","3xl","4xl","5xl"]},"disk_gb":{"type":"integer","minimum":1,"maximum":4096}},"required":["engine"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/connect":{"post":{"tags":["Databases"],"summary":"Connect an app to a database","description":"Gives the service named in the body the database's URL, as a reference (DATABASE_URL = ${{postgres.DATABASE_URL}}), and deploys it. A service that hasn't been deployed yet gets it with its first deploy.","operationId":"post_services_service_id_connect","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"service_id":{"type":"string","minLength":1,"maxLength":100}},"required":["service_id"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/projects/{project_id}/buckets":{"get":{"tags":["Buckets"],"summary":"List a project's buckets","description":"Each with its S3 endpoint, its name over S3, its access key id (not the secret), and what it holds.","operationId":"get_projects_project_id_buckets","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Buckets"],"summary":"Make a bucket","description":"A bucket in the project, with a key. The answer has the key's secret (secret_access_key): it isn't shown again, but services read it by reference. The name is what references name it by (${{uploads.S3_BUCKET}}); its name over S3 has a few characters of its own after it.","operationId":"post_projects_project_id_buckets","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string"},"description":"The project's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":32}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/buckets/{bucket_id}":{"get":{"tags":["Buckets"],"summary":"Get a bucket","operationId":"get_buckets_bucket_id","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"patch":{"tags":["Buckets"],"summary":"Change a bucket","description":"Its group on the project's canvas (`group_id`, null for none).","operationId":"patch_buckets_bucket_id","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"group_id":{"type":["string","null"],"maxLength":100}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Buckets"],"summary":"Delete a bucket","description":"Its keys stop working at once, and every object in it is deleted, for good, in the background.","operationId":"delete_buckets_bucket_id","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/buckets/{bucket_id}/rotate-key":{"post":{"tags":["Buckets"],"summary":"Give a bucket a new key","description":"The answer has the new secret. The old key keeps working for a day, so apps carry on until their next deploy gives them the new one.","operationId":"post_buckets_bucket_id_rotate_key","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/buckets/{bucket_id}/connect":{"post":{"tags":["Buckets"],"summary":"Connect an app to a bucket","description":"Gives the service named in the body S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY, as references to the bucket, and deploys it. A service that hasn't been deployed yet gets them with its first deploy.","operationId":"post_buckets_bucket_id_connect","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"service_id":{"type":"string","minLength":1,"maxLength":100}},"required":["service_id"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/buckets/{bucket_id}/objects":{"get":{"tags":["Buckets"],"summary":"List a bucket's files","description":"One level: the folders under `prefix` (keys up to the next `/`) and the files in it, up to 200 at a time; pass `cursor` for the next page.","operationId":"get_buckets_bucket_id_objects","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"delete":{"tags":["Buckets"],"summary":"Delete one file","description":"The file at `key`, for good.","operationId":"delete_buckets_bucket_id_objects","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/buckets/{bucket_id}/objects/link":{"get":{"tags":["Buckets"],"summary":"A link that opens one file","description":"A presigned URL for `key`, signed with the bucket's current key and good for ten minutes.","operationId":"get_buckets_bucket_id_objects_link","parameters":[{"name":"bucket_id","in":"path","required":true,"schema":{"type":"string"},"description":"The bucket's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/services/{service_id}/site/files":{"get":{"tags":["Services"],"summary":"A static site's files","description":"What the live deploy of a static site (kind `site`) serves, one folder at a time: its folders, and its files with their size and type.","operationId":"get_services_service_id_site_files","parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string"},"description":"The service's id."},{"name":"folder","in":"query","required":false,"description":"The folder to list, like assets/ (the top when left out).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/agent-tools":{"get":{"tags":["Agents"],"summary":"What agents can do","description":"Every MCP tool: the scope a token needs for it, and whether it also needs to be allowed to delete or to run commands.","operationId":"get_agent_tools","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/agent-tokens":{"get":{"tags":["Agents"],"summary":"List agent tokens","operationId":"get_organizations_organization_id_agent_tokens","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]},"post":{"tags":["Agents"],"summary":"Create an agent token","description":"For the MCP endpoint. The token is shown this once. Tokens that can deploy, and tokens owned by the organization, need an admin.","operationId":"post_organizations_organization_id_agent_tokens","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"scope":{"type":"string","enum":["read","deploy"]},"project_id":{"type":["string","null"]},"expires_in_days":{"type":["integer","null"],"minimum":1,"maximum":365},"can_run":{"type":"boolean"},"can_delete":{"type":"boolean"},"owner":{"type":"string","enum":["me","organization"]},"limits":{"type":["object","null"],"properties":{"writes_per_hour":{"type":"integer","minimum":1,"maximum":10000},"max_services_per_project":{"type":"integer","minimum":1,"maximum":500},"max_replicas":{"type":"integer","minimum":1,"maximum":20},"max_instances":{"type":"integer","minimum":1,"maximum":10}}}},"required":["name","scope"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is wrong; `details` says which fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/agent-tokens/{token_id}":{"delete":{"tags":["Agents"],"summary":"Revoke an agent token","operationId":"delete_agent_tokens_token_id","parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"string"},"description":"The token's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/agent-activity":{"get":{"tags":["Agents"],"summary":"What agents did","description":"The tools they called, newest first.","operationId":"get_organizations_organization_id_agent_activity","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/agent-approvals":{"get":{"tags":["Agents"],"summary":"List changes waiting for approval","operationId":"get_organizations_organization_id_agent_approvals","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/agent-approvals/{approval_id}/{decision}":{"post":{"tags":["Agents"],"summary":"Approve or reject an agent's change","description":"Admins. An approved change is carried out right away.","operationId":"post_agent_approvals_approval_id_decision","parameters":[{"name":"approval_id","in":"path","required":true,"schema":{"type":"string"},"description":"The approval's id."},{"name":"decision","in":"path","required":true,"schema":{"type":"string","enum":["approve","reject"]}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/organizations/{organization_id}/agent-connections":{"get":{"tags":["Agents"],"summary":"List agents signed in with OAuth","operationId":"get_organizations_organization_id_agent_connections","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string"},"description":"The organization's id."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{}}}},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}},"/api/v1/agent-connections/{grant_id}":{"delete":{"tags":["Agents"],"summary":"Disconnect an agent signed in with OAuth","operationId":"delete_agent_connections_grant_id","parameters":[{"name":"grant_id","in":"path","required":true,"schema":{"type":"string"},"description":"The grant's id."}],"responses":{"204":{"description":"Done. Nothing to return."},"401":{"description":"No token, or it expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed: not a member of the organization, or the role isn't enough.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not there, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearer":[]}]}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"The `access_token` from `POST /api/v1/auth/login`."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","examples":["validation_error"]},"message":{"type":"string"},"details":{}}}}}}}}