# Environments

An environment is one branch of one application, with its own URL, variables, and scaling. Every application has a production environment from birth; the rest you create.

With an [API key](/reference/api/authentication) the organisation is inferred. Session tokens must add `targetOrganisationId` to every body.

## List environments

```endpoint
POST /api/environments
> Every environment of one application. Returns an array, not a paginated envelope.
permission: read:projects

param applicationId | string | required | The application to read
param targetOrganisationId | string | session only | Required when using a session token

response 200 | Environments
[
  {
    "id": "env_7Tz",
    "application_id": "app_4kQ2",
    "name": "production",
    "github_branch": "main",
    "is_production": true,
    "status": "healthy",
    "url": "https://main-shop-acme.light-cloud.io",
    "custom_domain": "shop.example.com",
    "created_at": "2026-08-01T09:14:00.000Z",
    "updated_at": "2026-08-28T07:02:00.000Z"
  }
]

code curl
curl -X POST https://api.light-cloud.com/api/environments \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"applicationId": "app_4kQ2"}'

code Node
const res = await fetch("https://api.light-cloud.com/api/environments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ applicationId: "app_4kQ2" }),
});

if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);

const environments = await res.json();
environments.forEach((e) => console.log(e.name, e.github_branch, e.url));

code Python
import os
import requests

res = requests.post(
    "https://api.light-cloud.com/api/environments",
    headers={"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"},
    json={"applicationId": "app_4kQ2"},
)
res.raise_for_status()

for e in res.json():
    print(e["name"], e["github_branch"], e["url"])

code Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.light-cloud.com/api/environments"))
    .header("Authorization", "Bearer " + System.getenv("LIGHT_CLOUD_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(
        "{\"applicationId\":\"app_4kQ2\"}"))
    .build();

var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.body());
```

## Retrieve an environment

```endpoint
POST /api/environments/get
> One environment, including its `environment_vars`.
permission: read:projects

param environmentId | string | required | The environment to read
param targetOrganisationId | string | session only | Required when using a session token

response 200 | An environment
{
  "id": "env_7Tz",
  "application_id": "app_4kQ2",
  "name": "production",
  "github_branch": "main",
  "is_production": true,
  "status": "healthy",
  "url": "https://main-shop-acme.light-cloud.io",
  "environment_vars": { "NODE_ENV": "production" },
  "min_instances": 0,
  "max_instances": 5
}

code curl
curl -X POST https://api.light-cloud.com/api/environments/get \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environmentId": "env_7Tz"}'

code Node
const res = await fetch("https://api.light-cloud.com/api/environments/get", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ environmentId: "env_7Tz" }),
});

const environment = await res.json();
console.log(environment.environment_vars);

code Python
import os
import requests

res = requests.post(
    "https://api.light-cloud.com/api/environments/get",
    headers={"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"},
    json={"environmentId": "env_7Tz"},
)
res.raise_for_status()

print(res.json()["environment_vars"])
```

## Retrieve status

```endpoint
POST /api/environments/status
> Cheaper than retrieve. Poll this while a deploy runs.
permission: read:projects

param environmentId | string | required | The environment to poll
param targetOrganisationId | string | session only | Required when using a session token

response 200 | Current status
{ "id": "env_7Tz", "status": "deploying" }

code curl
until [ "$(curl -sS -X POST https://api.light-cloud.com/api/environments/status \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"environmentId\":\"$ENV_ID\"}" | jq -r .status)" = "healthy" ]; do
  sleep 10
done

code Node
const settled = ["healthy", "failed", "degraded"];
let status = "pending";

while (!settled.includes(status)) {
  await new Promise((resolve) => setTimeout(resolve, 10_000));

  const res = await fetch(
    "https://api.light-cloud.com/api/environments/status",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ environmentId }),
    }
  );

  ({ status } = await res.json());
}

if (status !== "healthy") throw new Error(`Deploy ended ${status}`);

code Python
import os
import time
import requests

SETTLED = {"healthy", "failed", "degraded"}
headers = {"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"}
status = "pending"

while status not in SETTLED:
    time.sleep(10)
    res = requests.post(
        "https://api.light-cloud.com/api/environments/status",
        headers=headers,
        json={"environmentId": environment_id},
    )
    res.raise_for_status()
    status = res.json()["status"]

if status != "healthy":
    raise RuntimeError(f"Deploy ended {status}")
```

## Create an environment

```endpoint
POST /api/environments/create
> Adds a branch environment to an application. See preview environments for what the platform creates on its own.
permission: create:projects

param applicationId | string | required | The application to add it to
param name | string | required | Becomes part of the URL
param githubBranch | string | required | The branch this environment tracks
param isProduction | boolean | false | Whether this is the production environment
param autoDeploy | boolean | optional | Deploy on every push to the branch
param buildCommand | string | inherited | Falls back to the application's
param outputDirectory | string | inherited | Falls back to the application's
param environmentVars | object | optional | String values only
param containerPort | number | optional | Container only
param memory | string | optional | Container only
param cpu | string | optional | Container only
param minInstances | number | 0 | 0 to 5
param maxInstances | number | 10 | 1 to 100, plan-capped
param customDomain | string | optional | Can be added later

response 201 | The created environment
{
  "id": "env_2Bd",
  "application_id": "app_4kQ2",
  "name": "staging",
  "github_branch": "develop",
  "is_production": false,
  "status": "pending"
}

code curl
curl -X POST https://api.light-cloud.com/api/environments/create \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "applicationId": "app_4kQ2",
    "name": "staging",
    "githubBranch": "develop",
    "autoDeploy": true,
    "environmentVars": { "NODE_ENV": "staging" }
  }'

code Node
const res = await fetch("https://api.light-cloud.com/api/environments/create", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    applicationId: "app_4kQ2",
    name: "staging",
    githubBranch: "develop",
    autoDeploy: true,
    environmentVars: { NODE_ENV: "staging" },
  }),
});

if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const environment = await res.json();

code Python
import os
import requests

res = requests.post(
    "https://api.light-cloud.com/api/environments/create",
    headers={"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"},
    json={
        "applicationId": "app_4kQ2",
        "name": "staging",
        "githubBranch": "develop",
        "autoDeploy": True,
        "environmentVars": {"NODE_ENV": "staging"},
    },
)
res.raise_for_status()

environment = res.json()
```

## Update an environment

```endpoint
POST /api/environments/update
> Changes settings or variables. Variables take effect on the next build, so deploy afterwards.
permission: update:projects

param environmentId | string | required | The environment to update
param name | string | optional | Renaming changes the URL
param buildCommand | string | optional | Command that produces the build
param outputDirectory | string | optional | Static only
param environmentVars | object | optional | Replaces the whole set — see the warning below
param containerPort | number | optional | Container only
param memory | string | optional | Container only
param cpu | string | optional | Container only
param minInstances | number | optional | 0 to 5
param maxInstances | number | optional | 1 to 100, plan-capped
param region | string | optional | See Limits
param customDomain | string | optional | Attached hostname
param autoDeploy | boolean | optional | Deploy on every push

response 200 | The updated environment
{ "id": "env_7Tz", "environment_vars": { "NODE_ENV": "production", "FEATURE_CHECKOUT_V2": "true" } }

code curl
API=https://api.light-cloud.com
AUTH=(-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" -H "Content-Type: application/json")

# Read, merge, write — sending a partial map deletes the rest
current=$(curl -sS -X POST "$API/api/environments/get" "${AUTH[@]}" \
  -d "{\"environmentId\":\"$ENV_ID\"}")

vars=$(echo "$current" | jq '.environment_vars + {"FEATURE_CHECKOUT_V2":"true"}')

curl -sS -X POST "$API/api/environments/update" "${AUTH[@]}" \
  -d "{\"environmentId\":\"$ENV_ID\",\"environmentVars\":$vars}"

curl -sS -X POST "$API/api/environments/deploy" "${AUTH[@]}" \
  -d "{\"environmentId\":\"$ENV_ID\"}"

code Node
const API = "https://api.light-cloud.com";
const headers = {
  Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
  "Content-Type": "application/json",
};

const post = async (path, body) => {
  const res = await fetch(API + path, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  return res.json();
};

// Read, merge, write — sending a partial map deletes the rest
const current = await post("/api/environments/get", { environmentId });

await post("/api/environments/update", {
  environmentId,
  environmentVars: {
    ...current.environment_vars,
    FEATURE_CHECKOUT_V2: "true",
  },
});

await post("/api/environments/deploy", { environmentId });

code Python
import os
import requests

API = "https://api.light-cloud.com"
headers = {"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"}

def post(path, **body):
    res = requests.post(API + path, headers=headers, json=body)
    res.raise_for_status()
    return res.json()

# Read, merge, write — sending a partial map deletes the rest
current = post("/api/environments/get", environmentId=environment_id)

post(
    "/api/environments/update",
    environmentId=environment_id,
    environmentVars={**current["environment_vars"], "FEATURE_CHECKOUT_V2": "true"},
)

post("/api/environments/deploy", environmentId=environment_id)
```

> [!WARNING]
> `environmentVars` replaces the whole set, it does not merge. Send only the keys you want and every variable you left out is deleted. Read the environment first, merge, then send it all back.

## Scale

```endpoint
POST /api/environments/scale
> Changes the instance range without a rebuild. Sending neither bound is a 400.
permission: update:projects

param environmentId | string | required | The environment to scale
param minInstances | number | one of two | 0 to 5. Above 0 keeps instances warm
param maxInstances | number | one of two | 1 to 100, plan-capped
param targetOrganisationId | string | session only | Required when using a session token

response 200 | The updated environment
{ "id": "env_7Tz", "min_instances": 1, "max_instances": 20 }

code curl
curl -X POST https://api.light-cloud.com/api/environments/scale \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environmentId": "env_7Tz", "minInstances": 1, "maxInstances": 20}'

code Node
// Keep one instance warm before a launch
const res = await fetch("https://api.light-cloud.com/api/environments/scale", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    environmentId: "env_7Tz",
    minInstances: 1,
    maxInstances: 20,
  }),
});

if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);

code Python
import os
import requests

requests.post(
    "https://api.light-cloud.com/api/environments/scale",
    headers={"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"},
    json={"environmentId": "env_7Tz", "minInstances": 1, "maxInstances": 20},
).raise_for_status()
```

## Deploy

```endpoint
POST /api/environments/deploy
> Builds the environment's branch at its current head and releases it. Returns as soon as the deployment starts.
permission: update:projects

param environmentId | string | required | The environment to deploy
param targetOrganisationId | string | session only | Required when using a session token

response 200 | The deployment that just started
{ "id": "dep_9f2a", "environment_id": "env_7Tz", "status": "pending" }

code curl
curl -X POST https://api.light-cloud.com/api/environments/deploy \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environmentId": "env_7Tz"}'

code Node
const res = await fetch("https://api.light-cloud.com/api/environments/deploy", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ environmentId: "env_7Tz" }),
});

const deployment = await res.json();
console.log("started", deployment.id);
```

## Delete an environment

```endpoint
POST /api/environments/delete
> Removes the environment and everything running in it. The production environment goes only with its application.
permission: delete:projects

param environmentId | string | required | The environment to delete
param targetOrganisationId | string | session only | Required when using a session token

response 200 | Deletion accepted
{ "message": "Environment deleted" }

code curl
curl -X POST https://api.light-cloud.com/api/environments/delete \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environmentId": "env_2Bd"}'
```

## Read logs

```endpoint
POST /api/environments/logs
> Runtime logs, kept 7 days. Returns an array of log lines.
permission: read:projects

param environmentId | string | required | The environment to read
param filters.startTime | string | optional | RFC 3339 timestamp
param filters.endTime | string | optional | RFC 3339 timestamp
param filters.severity | string[] | optional | Severity names, OR-ed together
param filters.textSearch | string | optional | Substring match on the message
param targetOrganisationId | string | session only | Required when using a session token

response 200 | Log lines
[
  "2026-08-28T09:41:02Z ERROR checkout: payment intent expired",
  "2026-08-28T09:41:02Z ERROR checkout: returning 502 to client"
]

code curl
curl -X POST https://api.light-cloud.com/api/environments/logs \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"environmentId\": \"env_7Tz\",
    \"filters\": {
      \"startTime\": \"$(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)\",
      \"severity\": [\"ERROR\", \"CRITICAL\"],
      \"textSearch\": \"checkout\"
    }
  }"

code Node
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();

const res = await fetch("https://api.light-cloud.com/api/environments/logs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    environmentId: "env_7Tz",
    filters: {
      startTime: oneHourAgo,
      severity: ["ERROR", "CRITICAL"],
      textSearch: "checkout",
    },
  }),
});

const lines = await res.json();
console.log(lines.join("\n"));

code Python
import os
import requests
from datetime import datetime, timedelta, timezone

one_hour_ago = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()

res = requests.post(
    "https://api.light-cloud.com/api/environments/logs",
    headers={"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"},
    json={
        "environmentId": "env_7Tz",
        "filters": {
            "startTime": one_hour_ago,
            "severity": ["ERROR", "CRITICAL"],
            "textSearch": "checkout",
        },
    },
)
res.raise_for_status()

print("\n".join(res.json()))
```

For a live tail there is a server-sent events stream:

```
GET /api/environments/:targetOrganisationId/:environmentId/logs/stream
```

## Read detailed metrics

```endpoint
POST /api/environments/metrics/detailed
> Time series for one environment. Omitting `metrics` returns all of them.
permission: read:projects

param environmentId | string | required | The environment to measure
param timeRange | string | required | `1h`, `6h`, `24h` or `7d`
param metrics | string[] | optional | `cpu`, `memory`, `requestCount`, `latency`, `instanceCount`, `bandwidthOut`, `bandwidthIn`
param targetOrganisationId | string | session only | Required when using a session token

response 200 | Series per metric
{
  "environmentId": "env_7Tz",
  "deploymentType": "container",
  "timeRange": {
    "startTime": "2026-08-27T09:00:00.000Z",
    "endTime": "2026-08-28T09:00:00.000Z"
  },
  "metrics": {
    "cpu": [{ "t": "2026-08-28T08:00:00.000Z", "v": 0.34 }],
    "requestCount": [{ "t": "2026-08-28T08:00:00.000Z", "v": 1284 }]
  }
}

code curl
curl -X POST https://api.light-cloud.com/api/environments/metrics/detailed \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "environmentId": "env_7Tz",
    "timeRange": "24h",
    "metrics": ["cpu", "memory", "requestCount"]
  }'

code Node
const res = await fetch(
  "https://api.light-cloud.com/api/environments/metrics/detailed",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      environmentId: "env_7Tz",
      timeRange: "24h",
      metrics: ["cpu", "memory", "requestCount"],
    }),
  }
);

const { metrics } = await res.json();

code Python
import os
import requests

res = requests.post(
    "https://api.light-cloud.com/api/environments/metrics/detailed",
    headers={"Authorization": f"Bearer {os.environ['LIGHT_CLOUD_API_KEY']}"},
    json={
        "environmentId": "env_7Tz",
        "timeRange": "24h",
        "metrics": ["cpu", "memory", "requestCount"],
    },
)
res.raise_for_status()

metrics = res.json()["metrics"]
```

There is also `POST /api/environments/metrics/sparkline`, which takes `environmentIds` (an array) and returns small series for several environments at once, for list views.

## Read activity

```endpoint
POST /api/environments/activity
> Settings and variable changes, without the values.
permission: read:projects

param environmentId | string | required | The environment to read
param limit | number | optional | How many entries to return
param offset | number | optional | Skip this many

response 200 | Activity entries
[
  {
    "id": "act_5Wq",
    "action": "environment_vars_updated",
    "actor_email": "you@example.com",
    "created_at": "2026-08-28T07:00:00.000Z"
  }
]

code curl
curl -X POST https://api.light-cloud.com/api/environments/activity \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environmentId": "env_7Tz", "limit": 20}'
```

## Password protection

```endpoint
POST /api/environments/password
> Puts a password wall in front of the environment's URL. `enabled` must be a boolean, not a string.
permission: update:projects

param environmentId | string | required | The environment to protect
param enabled | boolean | required | Turns the wall on or off
param password | string | when enabling | The password visitors must enter

response 200 | Protection state
{ "id": "env_2Bd", "password_protected": true }

code curl
curl -X POST https://api.light-cloud.com/api/environments/password \
  -H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environmentId": "env_2Bd", "enabled": true, "password": "preview-only"}'

code Node
const res = await fetch("https://api.light-cloud.com/api/environments/password", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_CLOUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    environmentId: "env_2Bd",
    enabled: true,
    password: "preview-only",
  }),
});
```

Visitors exchange the password for access at `POST /api/environments/unlock`, with body `environmentId` and `password`. That endpoint takes no bearer token — it is what the wall itself calls, and it is rate limited per address.

## Domains

`check-domain`, `add-domain`, and `retry-domain` also exist under `/api/environments`, with the same bodies as their [application](/reference/api/applications#custom-domains) counterparts, addressed by `environmentId`.

## Related

- [Applications](/reference/api/applications): The parent resource.
- [Deployments](/reference/api/deployments): What a deploy produced.
- [Environment variables](/platform/environment-variables): Build-time versus runtime.
