API overview
How the opencloudport HTTP API works - keys, requests, errors, ids and lists.
Everything you can do in the dashboard, you can do over HTTP. The API is plain JSON over HTTPS at:
https://opencloudport.com/api/v1The TypeScript SDK wraps it, and AI agents use the same actions over MCP.
Keys
Send your key in the Authorization header:
curl https://opencloudport.com/api/v1/workspace \
-H "Authorization: Bearer $OCP_KEY"GET /api/v1/workspace is a handy first call: it answers with the workspace the key belongs to (id, name, slug).
There are two kinds of key:
| Key | Starts with | Reaches | Made on |
|---|---|---|---|
| Workspace key | ocp_ | Everything in the workspace. | The API keys page. |
| App key | ocps_ | One app, or one service in it, optionally read-only. | The app's Settings tab, or POST /apps/{id}/keys. |
A read-only key can only make GET requests. A key used outside its reach gets 403 key_out_of_scope. Any key can have a spending limit; once reached, changes get 402 key_spend_limit_reached and reads still work.
Every change a key makes (anything but GET) is written to its activity log, with what was done and whether it worked. Request bodies are never recorded.
Requests
- Send JSON with
Content-Type: application/json. Deployments are the one exception: they'remultipart/form-data. - Bodies are checked strictly: an unknown field is an error, so typos don't pass silently.
- Bodies are limited to 16 KB, except where an endpoint says otherwise (SQL 128 KB, email 1 MB, sandbox files 25 MB, deployments 10 MB of code plus 50 MB of static files). Every limit is on one page: Limits.
- Ids in paths are always ids (
app_…,svc_…), never names. Variable names and sandbox process names are the only names in paths.
Responses
One object comes back in data:
{ "data": { "id": "app_7QxV…", "object": "app", "name": "Bakery orders", "status": "active" } }Lists come back as:
{ "object": "list", "data": [ … ], "has_more": false }Apps and deployments are paged: pass limit (1 to 100, default 20) and starting_after with the last id you got. Files in a bucket use cursor and next_cursor instead.
Money is in US dollars, as exact decimal strings ("12.500000"), with the same amount in millionths of a dollar alongside (balance_micros). Times are ISO 8601 in UTC.
Errors
Every error has the same shape and a matching HTTP status:
{ "error": { "code": "name_taken", "message": "This app already has a service named \"DB\". Pick another name." } }Check code in your code; message is written for people and may change. When a field is wrong, its name starts the message ("name: name is required").
| Status | Common codes | What to do |
|---|---|---|
| 400 | invalid_request, invalid_json, query_failed | Fix the request. The message says what's wrong. |
| 401 | missing_api_key, invalid_api_key | Send a valid key. |
| 402 | insufficient_balance, app_paused, key_spend_limit_reached | Add funds, or raise the key's limit. |
| 403 | key_out_of_scope | Use a key that reaches this app or service. |
| 403 | limited_key | This key has a spending limit, so it can only tighten limits, make read-only keys and pause. Do it in the dashboard, or with a key that has no limit. |
| 404 | not_found | The id doesn't exist in this workspace. |
| 409 | name_taken, app_deleted, compute_not_ready, idempotency_in_progress, … | The app isn't in a state for this yet, or the same request is still running. The message says why. |
| 413, 415 | body_too_large, unsupported_media_type | Send less, or send JSON. |
| 422 | idempotency_key_reused | This Idempotency-Key was used for a different request. Use a new key. |
| 429 | rate_limited | Too many requests with this key. Wait for the seconds in Retry-After, then try again. |
| 502, 503 | deployment_failed, sandbox_unavailable, … | Something upstream failed. Try again shortly. |
Ids
| Thing | Looks like |
|---|---|
| App | app_ + 24 characters |
| Service | svc_ + 24 characters |
| Deployment | dep_ + 24 characters |
| App key | key_ + 24 characters |
| Sandbox | sbx + 20 lowercase letters and digits |
msg_ + 32 characters |
Retrying safely
Send an Idempotency-Key header with any POST you might retry: sending an email, deploying, creating an app or a sandbox. Use a new random value (a UUID) for each new request, and the same one when you retry it.
curl -X POST https://opencloudport.com/api/v1/apps/$APP_ID/services/$EMAIL_ID/send \
-H "Authorization: Bearer $OCP_KEY" \
-H "Idempotency-Key: 3f6c1a52-8d7e-4b1f-9c0a-5e2d7b9f4a11" \
-H "Content-Type: application/json" \
-d '{ "to": "ann@example.com", "subject": "Your order", "text": "Thanks!" }'- A retry with the same key and the same request gets the first answer back, with the header
Idempotent-Replayed: true, and nothing happens twice: one email, one deployment. - The same key with a different request is refused with
422 idempotency_key_reused. - A retry that arrives while the first request is still running gets
409 idempotency_in_progress; wait a moment and retry. - Keys are remembered for 24 hours, separately for each API key. Answers that were server errors (5xx) aren't remembered, so retrying those really tries again.
These are also safe to repeat, with or without the header:
POST /appswith the sameexternal_idreturns the app you already made.- Deleting an app, service, sandbox or file that's already gone succeeds.
PUT /apps/{id}/env/{NAME}sets the same value again.
Rate limits
Each API key has its own limit, so one busy key never slows another. Calls made by an AI agent through MCP count towards its key the same way.
| What | Average | Bursts of up to |
|---|---|---|
| Every request | 20 a second | 60 |
| Sending email | 2 a second | 20 |
| Starting a sandbox | 1 every 5 seconds | 5 |
Over the limit, you get 429 rate_limited with a Retry-After header in seconds. Waiting that long and retrying always works.
Money checks
Two calls need a positive balance, and get 402 insufficient_balance without one: adding a service, and starting a sandbox. Creating an app is free. While a workspace is paused for lack of credit, sending email and using sandboxes get 402 app_paused. See How billing works.
opencloudport