The Light Cloud HTTP API - base URL, bearer tokens, the organisation scope every call needs, request and response conventions, errors, and rate limits.
API
Everything the console does, it does over a public HTTP API. The same API backs the MCP server, so anything an AI agent can do to your account, a script can do too.
Base URL:
https://api.light-cloud.com
Every path below is relative to it. All requests and responses are JSON; send Content-Type: application/json on anything with a body.
Note
This API is stable enough to build on but is not versioned yet. Endpoints are added more often than they change, and breaking changes are announced on the blog before they ship.
Authenticating#
Create an API key in the console - left sidebar → your workspace name → API keys → New key - and send it as a bearer token:
Authorization: Bearer lc_...
curl -X POST https://api.light-cloud.com/api/applications \
-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit":100}'
The secret is shown once, at creation. Keys require a paid plan; the MCP server works on every account including free. See Authentication for access levels, expiry, revocation, and what a key deliberately cannot do.
A worked example#
Deploy a repository and wait for it to go live, start to finish. Each tab is self-contained — no SDK, no shared setup.
#!/usr/bin/env bash
set -euo pipefail
API=https://api.light-cloud.com
AUTH=(-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" -H "Content-Type: application/json")
# 1. Let detection work out the build settings rather than guessing them
curl -sS -X POST "$API/api/applications/detect-framework" "${AUTH[@]}" -d '{"owner":"acme","repo":"shop","branch":"main"}'
# 2. Create it. The production environment comes with it, and the first
# build starts immediately.
app=$(curl -sS -X POST "$API/api/applications/create" "${AUTH[@]}" -d '{
"name": "shop",
"githubRepoUrl": "https://github.com/acme/shop",
"githubBranch": "main",
"deploymentType": "container",
"framework": "nextjs",
"runtime": "nodejs",
"buildCommand": "npm run build",
"environmentVars": { "NODE_ENV": "production" }
}')
APP_ID=$(echo "$app" | jq -r .id)
# 3. Poll until the build settles
while :; do
status=$(curl -sS -X POST "$API/api/applications/status" "${AUTH[@]}" -d "{"applicationId":"$APP_ID"}" | jq -r .status)
case "$status" in
healthy) break ;;
failed|degraded) echo "build failed"; exit 1 ;;
esac
sleep 10
done
curl -sS -X POST "$API/api/applications/get" "${AUTH[@]}" -d "{"applicationId":"$APP_ID"}" | jq -r .url
# https://main-shop-acme.light-cloud.ioThere is no targetOrganisationId anywhere above: a key carries its own organisation. With a session token every body would need it.
Deploying from CI#
The case API keys exist for. Store the key as a repository secret, never in the workflow file.
# .github/workflows/deploy.yml
name: Deploy to Light Cloud
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger deploy
env:
LIGHT_CLOUD_API_KEY: ${{ secrets.LIGHT_CLOUD_API_KEY }}
APP_ID: ${{ vars.LIGHT_CLOUD_APP_ID }}
run: |
curl -sS --fail-with-body \
-X POST https://api.light-cloud.com/api/applications/deploy \
-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"applicationId\":\"$APP_ID\"}"
Tip
If the repository is connected through the GitHub App, pushes already deploy on their own and this workflow is redundant. Use the API when the trigger is something else - a release, a manual approval, a green run in another pipeline.
Organisation scope#
A key belongs to one organisation, so calls made with one need nothing extra - targetOrganisationId is inferred, and passing a different organisation's id is refused.
Sessions are not scoped that way. A person can belong to several organisations, so a call authenticated with a session token must name the one it means:
{ "targetOrganisationId": "org_...", "limit": 100 }
Read your organisations from the profile endpoint:
curl https://api.light-cloud.com/api/auth/profile \
-H "Authorization: Bearer $TOKEN"
{
"id": "usr_...",
"email": "you@example.com",
"organisations": [
{ "id": "org_...", "name": "Acme Labs", "slug": "acme-labs", "role": "owner" }
]
}
Your role - or the key's access level - decides what the API lets you do. Each endpoint below names the permission it checks; see Errors for the list.
POST, mostly#
The API is RPC-shaped rather than REST-shaped. Reads and writes are both POST with a JSON body, and the verb lives in the path:
POST /api/applications list
POST /api/applications/get read one
POST /api/applications/create create
POST /api/applications/delete delete
GET is used only where there is no body to send - the profile, the GitHub integration lookups, and platform config. There is no PATCH, and PUT appears only under /api/profile.
Tip
Resource ids are opaque strings. Never build one; always read it back from a list or create call.
Responses#
A successful call returns the resource itself, unwrapped:
{ "id": "app_...", "name": "shop", "status": "healthy" }
List endpoints that paginate return an envelope:
{ "items": [], "totalItems": 0, "totalPages": 0, "currentPage": 1 }
Failures return an HTTP error status and a message, sometimes with a code:
{ "message": "Organisation ID is required." }
See Errors for the status codes and what to do about each.
Pagination#
Endpoints that return an envelope accept these in the body:
| Field | Type | Default | Range |
|---|---|---|---|
page | number | 1 | 1-100000 |
limit | number | 10 | 1-100 |
filter | string | none | free text, matched against the name |
sortColumn | string | created_at | |
sortOrder | string | desc | asc or desc |
Out-of-range values are clamped to the nearest legal one rather than rejected.
Rate limits#
5000 requests per 10 minutes per IP, across all endpoints. Standard RateLimit-* headers come back on every response; a breach is a 429.
Sign-in and MFA verification are limited more tightly and answer 429 with a Retry-After header in seconds.
CORS#
Requests with no Origin - curl, CI runners, servers, CLIs - are always allowed. Browser requests are allowed only from origins on the platform allowlist, so a browser app you host yourself cannot call this API directly. Put your own backend in front of it.
Health check#
/api/healthWhether the API is up. No authentication — useful as a reachability check before blaming your token.
curl https://api.light-cloud.com/api/health{
"status": "ok",
"timestamp": "2026-08-28T13:31:20.268Z",
"uptime": 5417.98
}Platform catalogue#
Read these rather than hardcoding sizes, regions or tiers. The Limits page is written from the same source, but the endpoint is the one that stays current.
/api/config/platformRegions, container sizes, database tiers and the rest of the catalogue the console fills its pickers from.
curl https://api.light-cloud.com/api/config/platform \
-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY"{
"regions": [
{ "id": "europe-west1", "label": "Belgium", "tier": 1 },
{ "id": "us-central1", "label": "Iowa", "tier": 1 }
],
"containerSizes": [
{ "id": "micro", "vcpu": 1, "memory": "512Mi", "maxConcurrency": 80 }
]
}/api/config/feature-flagsWhich optional features are on for your account. Some endpoints — the data explorer and database dumps — answer with a "not enabled" message when their flag is off, and this is where you check.
curl https://api.light-cloud.com/api/config/feature-flags \
-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY"{
"flags": ["database.shared-pool", "console.github-deploy-feedback"],
"map": {
"database.shared-pool": true,
"console.gitlab": true,
"console.bitbucket": true
}
}/api/config/cloudrunContainer sizes and scaling ranges, with the memory and CPU combinations that are actually valid together.
curl https://api.light-cloud.com/api/config/cloudrun \
-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY"{
"sizes": [
{ "id": "micro", "memory": "512Mi", "cpu": "1", "maxConcurrency": 80 }
],
"scaling": { "minInstances": [0, 5], "maxInstances": [1, 100] }
}There is also GET /api/config/cloudrun/compatible?memory=<value>, which narrows the CPU options to those valid for a given memory setting.