opencloudportDocs

Apps and services

Create apps, find them again, and plug in the services they need.

The app object

{
  "id": "app_7QxV2nLk0PqR8sTuVwXyZa1b",
  "object": "app",
  "name": "Bakery orders",
  "status": "active",
  "url": "https://ocp-aabd5c1cfc91d7b629a0.<apps domain>",
  "addresses": [
    { "url": "https://ocp-aabd5c1cfc91d7b629a0.<apps domain>", "kind": "default", "status": "active" },
    { "url": "https://bakery.yourapps.com", "kind": "platform", "status": "active" }
  ],
  "suspended_reason": null,
  "paused_by": null,
  "paused_at": null,
  "pause_note": null,
  "monthly_spend_limit": "25.000000",
  "external_id": "customer_42",
  "metadata": { "plan": "pro" },
  "services": [{ "id": "svc_…", "type": "database", "name": "DB", "status": "active" }],
  "current_deployment_id": "dep_…",
  "created_at": "2026-10-21T10:58:00.000Z",
  "updated_at": "2026-10-21T10:58:00.000Z",
  "deleted_at": null
}

status is active, suspended (paused) or deleted. paused_by says why a paused app is paused:

paused_byWhyIt runs again when
ownerYou (or an agent) paused it, with an optional pause_note only you see. paused_at is when.You resume it.
limitIt reached its monthly_spend_limit.The month turns (UTC), or you raise or remove the limit.
workspaceThe workspace ran out of credit.Funds are added.

More than one can apply at once; the app serves only when none does, and paused_by shows the first of them in that order. (suspended_reason says the same with the older names: owner, limit, balance.)

Create an app

POST /api/v1/apps

Prop

Type

Returns 201 with the new app. It starts with no services. Creating an app is free and needs no balance; adding services does. Each workspace can have up to 1,000 apps.

curl https://opencloudport.com/api/v1/apps \
  -H "Authorization: Bearer $OCP_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Bakery orders", "external_id": "customer_42"}'

List apps

GET /api/v1/apps, newest first.

Prop

Type

Get, rename or delete an app

EndpointWhat it does
GET /api/v1/apps/{id}One app, including deleted ones.
PATCH /api/v1/apps/{id}Send name and/or metadata. metadata replaces what was there.
DELETE /api/v1/apps/{id}Deletes the app, all its services and their data, and its sandboxes. Returns the deleted app.

Pause and resume an app

EndpointWhat it does
POST /api/v1/apps/{id}/pausePauses it. Optional body { "note": "Fixing checkout" } (up to 500 characters, for you; visitors never see it). Returns the app.
POST /api/v1/apps/{id}/resumeUndoes your pause. Returns the app, which stays paused if its limit or the workspace's balance still holds it. Workspace keys only.

While paused, visitors get "This app is paused." (503), its sandboxes stop, and new sandboxes, emails and builds are refused (app_paused; app_limit_reached for a limit). An app you paused takes no deploys either; one paused for money still does, so a fix is ready when it runs again. Its data is kept and can still be read through the API. An app's own key can pause it but not resume it, so an agent can stop an app but only a person starts it again.

Monthly spending limit

EndpointWhat it does
GET /api/v1/apps/{id}/spend-limitThe limit, what the app spent this month, and whether it reached the limit.
PUT /api/v1/apps/{id}/spend-limit{ "monthly_limit": "25.00" } sets it (a number or a decimal string, to the cent); null removes it. Workspace keys only.
{ "data": { "object": "app_spend", "month": "2026-10", "monthly_limit": "25.000000", "spent_this_month": "3.418200", "spent_this_month_micros": 3418200, "limit_reached": false } }

Months are calendar months in UTC. Spending is everything the app's usage costs (requests, database, files, email, sandboxes, builds), priced as on your bill and checked every 10 minutes, so an app can go a little over before it pauses. Setting a limit below what's already spent pauses the app straight away.

For limits per customer rather than per app, see end users.

Scheduled runs

GET /api/v1/apps/{id}/schedules lists the app's schedules; PUT with { "schedules": [{ "cron": "0 9 * * *", "enabled"?: true }] } replaces them all (at most 5; [] removes them). Each calls the app's code's scheduled() handler; expressions are five-field cron in UTC. Each schedule comes back as { id, cron, description, enabled, next_run_at, last_run_at, last_result, last_detail }, where last_result is ok, error or skipped and last_detail says why. A mistake in any expression is 400 invalid_schedule, naming it, and changes nothing. See Scheduled runs.

The service object

{
  "id": "svc_…",
  "object": "service",
  "app_id": "app_…",
  "type": "database",
  "name": "DB",
  "status": "active",
  "created_at": "…",
  "deleted_at": null
}

status goes from pending to active within seconds (or failed). name is how the app's code reaches it (env.DB); Compute and Sandboxes have no name.

Add a service

POST /api/v1/apps/{id}/services

Prop

Type

Returns 201 with the service, usually pending. Needs a positive balance.

ErrorWhen
409 compute_exists, 409 sandbox_existsAn app has at most one of each.
409 name_takenAnother service or a variable already has that name.
409 service_limit_reachedAn app can have up to 20 named services.

List, get or remove services

EndpointWhat it does
GET /api/v1/apps/{id}/servicesThe app's services, oldest first.
GET /api/v1/apps/{id}/services/{serviceId}One service.
DELETE /api/v1/apps/{id}/services/{serviceId}Removes it and deletes its data. Removing Compute also removes the app's variables; removing Sandboxes deletes its sandboxes.

Domains

An app's own addresses: custom domains, and its platform address under the workspace's platform domain. See Domains for the DNS side.

EndpointWhat it does
GET /api/v1/apps/{id}/domainsThe app's domains, oldest first. Pending ones are checked again first.
POST /api/v1/apps/{id}/domains{ "hostname": "shop.example.com" } adds a custom domain: 201, pending, with the records to add. Up to 20 per app.
GET /api/v1/apps/{id}/domains/{domainId}One domain, checked again if pending.
DELETE /api/v1/apps/{id}/domains/{domainId}Stops serving a custom domain.
PUT /api/v1/apps/{id}/slug{ "slug": "bakery" } gives the app bakery.<platform domain>, replacing its old one.
DELETE /api/v1/apps/{id}/slugRemoves the app's platform address.
GET / PUT / DELETE /api/v1/apps-domainThe workspace's platform domain: { "domain": "yourapps.com" }. Workspace keys only.

The domain object:

{
  "id": "dom_…",
  "object": "domain",
  "app_id": "app_…",
  "hostname": "shop.example.com",
  "url": "https://shop.example.com",
  "kind": "custom",
  "slug": null,
  "status": "pending",
  "ssl_status": "pending",
  "message": "Waiting for the domain to point here. Add the records below at your DNS provider; …",
  "records": [{ "type": "CNAME", "name": "shop.example.com", "value": "<target>", "purpose": "routing", "note": "Points the domain at your app. …" }],
  "checked_at": "2026-10-22T12:00:00.000Z",
  "created_at": "2026-10-22T12:00:00.000Z"
}

Errors: invalid_domain (422), domain_taken (409: another app has it, or it's under someone's platform domain), too_many_domains (409), slug_taken (409), no_platform_domain (409), platform_address (409: remove a platform address with DELETE /slug), domains_unavailable (503: not set up on this server).

On this page