Code and variables
Deploy code to an app's Compute, roll back, and set the variables it reads.
The app needs Compute first, and it must be ready (active). Otherwise these calls get 409 no_compute or 409 compute_not_ready.
Deploy
POST /api/v1/apps/{id}/deployments, as multipart/form-data:
- A
metadatafield with JSON:
Prop
Type
- One file field per module, named by its path (
index.js,lib/util.js). Types follow the extension:
| Extension | Becomes |
|---|---|
.js, .mjs | ES module |
.cjs | CommonJS module |
.wasm | WebAssembly |
.txt, .html, .sql | Text |
.bin | Binary data |
.map | Source map |
Up to 100 files and 10 MB. The request must say its Content-Length (curl and fetch do).
- Static files (HTML, CSS, scripts for the browser, images, fonts) as fields named
assets/<path>:assets/index.htmlis served at/,assets/css/site.cssat/css/site.css. Up to 20,000 files, 25 MB each and 50 MB in total per upload. Content types follow the extension. - Code whose own folder is called
assets(Vite puts a server build's chunks indist/server/assets/): name its fieldsmodules/<path>, which is always code.modules/assets/chunk.jsis the moduleassets/chunk.js.
Static files
Files are served before your code: a request for a file gets the file, and anything else reaches your code, which can also fetch files itself through env.ASSETS. With files and no code, it's a static site. assets_config works like Cloudflare's static assets settings:
Prop
Type
curl https://opencloudport.com/api/v1/apps/$APP_ID/deployments \
-H "Authorization: Bearer $OCP_KEY" \
-F 'metadata={"assets_config": {"not_found_handling": "single-page-application"}}' \
-F 'assets/index.html=@dist/index.html' \
-F 'assets/assets/app.js=@dist/assets/app.js'curl https://opencloudport.com/api/v1/apps/$APP_ID/deployments \
-H "Authorization: Bearer $OCP_KEY" \
-F 'metadata={"main_module": "index.js", "compatibility_flags": ["nodejs_compat"]}' \
-F 'index.js=@dist/index.js' \
-F 'lib/util.js=@dist/lib/util.js'Answers 201 once the code is live, with the deployment:
{
"data": {
"id": "dep_…",
"object": "deployment",
"app_id": "app_…",
"status": "active",
"main_module": "index.js",
"compatibility_date": "2026-10-01",
"compatibility_flags": ["nodejs_compat"],
"modules": [{ "name": "index.js", "type": "esm", "size": 1840, "sha256": "…" }],
"assets": { "count": 0, "size_bytes": 0 },
"assets_config": null,
"size_bytes": 1840,
"source_deployment_id": null,
"error": null,
"created_at": "…",
"finished_at": "…"
}
}Sending only what changed
A deploy with many static files (images, video, fonts) doesn't have to send them all again. Ask first which ones this workspace has already deployed:
curl https://opencloudport.com/api/v1/apps/$APP_ID/deployments/upload-session \
-H "Authorization: Bearer $OCP_KEY" \
-H "Content-Type: application/json" \
-d '{ "files": [{ "path": "index.html", "sha256": "…" }, { "path": "video/intro.mp4", "sha256": "…" }] }'It answers { "data": { "missing": ["index.html"], "known": ["video/intro.mp4"] } }. Then deploy with only the missing files, and list the known ones by hash in metadata.assets_manifest (path → sha256 of the file's content). Only what you send counts toward the 50 MB per upload. Only files this workspace has deployed before count as known. The SDK's deploy() and deploy_from_sandbox do this for you.
Files left out: .assetsignore
As on Cloudflare, a .assetsignore file at the top of your static files lists files not to publish, one gitignore-style pattern per line (wrangler.json, .dev.vars, *.map, drafts/, !keep.map). The .assetsignore file itself is never published either.
A deployment is deploying, active, superseded (a newer one replaced it) or failed. If the code is refused, you get 422 deployment_rejected with the reason, and the failed deployment is kept so you can look at it. Code that fails to start (an error at the top of a file, a missing module, a node: import without nodejs_compat) is refused this way, with the error, and the app keeps running its previous code.
Roll back
POST /api/v1/apps/{id}/deployments/{deploymentId}/redeploy deploys an earlier deployment's code and files again, as a new deployment whose source_deployment_id points at the original.
List and get deployments
| Endpoint | What it does |
|---|---|
GET /api/v1/apps/{id}/deployments | Newest first. Paged with limit and starting_after. |
GET /api/v1/apps/{id}/deployments/{deploymentId} | One deployment, with source: { "type": "git", repo, branch, commit, commit_message, build_id }, { "type": "sandbox", sandbox_id, folder, code_folder }, or null for an upload. |
To deploy what a sandbox built without uploading it yourself, see Deploy what a sandbox built.
Variables
Settings and secrets your code reads as env.NAME. Changes apply straight away, without a new deployment.
| Endpoint | What it does |
|---|---|
GET /api/v1/apps/{id}/env | Every variable. Secrets come back with value: null. |
PUT /api/v1/apps/{id}/env/{NAME} | Sets one. 201 if it's new, 200 if it replaced one. |
DELETE /api/v1/apps/{id}/env/{NAME} | Removes one. |
Prop
Type
curl -X PUT https://opencloudport.com/api/v1/apps/$APP_ID/env/STRIPE_KEY \
-H "Authorization: Bearer $OCP_KEY" -H "Content-Type: application/json" \
-d '{"value": "sk_live_…"}'Names are letters, digits and underscores, starting with a letter or underscore, and can't be a service's name. An app can have up to 100.
opencloudport