Preview Environments — API
Use these endpoints to configure and operate Preview Environments nested under a primary HTTP project. A preview has its own identity, URL, capacity, storage, and billing lifecycle, but it can only be accessed through the primary project it belongs to.
The operation is idempotent by organization, primary project, and previewKey. PUT and DELETE may return an asynchronous operation; query the operation endpoint until it reaches a terminal state.
Authentication and security
Include a credential with the corresponding project permission and organization context:
| Header | Required | Description |
|---|---|---|
x-api-key | Action operations | Primary project API Key kept in a secret. |
x-organization-id | Organization-token requests | Active project organization. The Action identifies the organization from the authenticated project. |
Content-Type: application/json | For requests with a body | Indicates a JSON body. |
The owner must enable Preview Environments in the console before a project API Key can operate previews. The key stays limited to the primary project and its configured guardrails: it cannot change settings, entitlements, limits, or disallowed plans.
Public responses never include environment variables, credentials, private image URLs, resource names, or internal operational details. Treat the API Key as a secret and never log it.
Base URL and identifiers
https://api.zenifra.com/v1In the examples, PROJECT_ID represents the primary HTTP project ID and API_KEY represents an API Key kept in a secret. Replace only these placeholders with values from your organization.
export PROJECT_ID="your-project-id"
export API_KEY="your-api-key"
export USER_TOKEN="your-user-token"
export ORGANIZATION_ID="your-organization-id"
export BASE_URL="https://api.zenifra.com/v1"previewKey may contain only letters, numbers, periods, underscores, and hyphens within the API's size limit. Do not silently alter a rejected key.
Endpoints
| Goal | Method and path |
|---|---|
| Read settings | GET /project/:projectId/preview-environments/settings |
| Update settings | PATCH /project/:projectId/preview-environments/settings |
| Read inherited plan | GET /project/:projectId/preview-environments/plans |
| Read preview costs | GET /project/:projectId/preview-environments/billing |
| List previews | GET /project/:projectId/preview-environments |
| Create or update a preview | PUT /project/:projectId/preview-environments/:previewKey |
| Read a preview | GET /project/:projectId/preview-environments/:previewKey |
| Remove a preview | DELETE /project/:projectId/preview-environments/:previewKey |
| Read an operation | GET /project/:projectId/preview-environments/:previewKey/operations/:operationId |
Project settings
Read settings
GET /project/:projectId/preview-environments/settingsReturns the primary project's opt-in and effective guardrails. The public response includes enabled, the project's inherited plan in default_plan/allowed_plans, default_ttl_hours, max_ttl_hours, and max_active_environments. A Preview cannot select a different plan from the primary project.
curl -sS "$BASE_URL/project/$PROJECT_ID/preview-environments/settings" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID"Example response:
{
"data": {
"enabled": true,
"default_plan": "basic",
"allowed_plans": ["basic"],
"default_ttl_hours": 24,
"max_ttl_hours": 168,
"max_active_environments": 5
}
}Update settings
PATCH /project/:projectId/preview-environments/settingsRequires project configuration permission. The API does not allow changing the organization entitlement or exceeding the effective limit.
{
"enabled": true,
"default_ttl_hours": 24,
"max_ttl_hours": 168,
"max_active_environments": 5
}default_ttl_hours and max_ttl_hours must be between 1 and 168, and the default cannot be greater than the maximum. The hourly price always comes from the primary project's plan.
Preview costs
GET /project/:projectId/preview-environments/billingRequires project.billing.read. Returns the accrued cost of active Previews, the current hourly rate, and an estimate until expiration for each environment and for the group.
Monetary values use BRL minor units, matching the existing billing contract. For example, 5.4 means R$ 0.054. accrued_amount includes hourly snapshots calculated through as_of; pending_amount identifies value awaiting settlement. estimated_until_expiration is an estimate, not a final charge, and does not include the current hour until the hourly usage job closes it.
{
"data": {
"currency": "brl",
"as_of": "2026-08-25T22:00:00.000Z",
"previews": [
{
"preview_id": "preview-id",
"key": "pr-42",
"status": "running",
"plan": "basic",
"hourly_rate": 5.4,
"accrued_amount": 10.8,
"settled_amount": 5.4,
"pending_amount": 5.4,
"estimated_until_expiration": 21.6,
"expires_at": "2026-08-26T02:00:00.000Z",
"billing_status": "pending"
}
],
"summary": {
"active_previews": 1,
"accrued_amount": 10.8,
"settled_amount": 5.4,
"pending_amount": 5.4,
"hourly_rate": 5.4,
"estimated_until_expiration": 21.6
}
}
}Plans
List preview plans
GET /project/:projectId/preview-environments/plansReturns only active plans allowed for previews in this project. Each item provides the plan identifier, public description, hourly price, and currency (BRL). The preview default and common plans appear only when explicitly allowed for this use.
curl -sS "$BASE_URL/project/$PROJECT_ID/preview-environments/plans" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID"The catalog price is the source of truth. Do not copy commercial values into the Action or a client application.
List previews
GET /project/:projectId/preview-environmentsLists previews nested under the primary project. Use the page and limit parameters for pagination.
curl -sS "$BASE_URL/project/$PROJECT_ID/preview-environments?page=1&limit=20" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID"Each item may contain these public fields:
| Field | Description |
|---|---|
id | Public preview identifier. |
key | Stable key identifying the environment in the project. |
status | Product state such as accepted, provisioning, available, deleting, deleted, or failed. |
url | Public URL when available. |
plan | Selected plan. |
payment_mode | The preview uses hourly billing. |
exposure | Exposure configured for the environment. |
inherit_envs | Always true: variables from the primary project are inherited. Values are never returned. |
updated_at and expires_at | Most recent update and current expiration. |
The list does not return environment variables, data, credentials, hashes, leases, counters, or resource names.
Create or update a preview
PUT /project/:projectId/preview-environments/:previewKeyCreates or updates the preview identified by previewKey. An upsert body accepts:
| Field | Type | Required | Description |
|---|---|---|---|
image | string | Yes for upsert | Reference to the ready-to-use image to publish. |
inherit_envs | boolean | No | Legacy compatibility field; the Preview always inherits user variables inside Zenifra. |
ttl_hours | integer | No | Duration in hours; default 24, minimum 1, maximum 168. |
The preview always uses the primary project's plan and synchronizes its port, exposure, and access rules. Storage is new, empty, and isolated. Data, custom domains, and custom image startup commands are not inherited.
curl -sS -X PUT \
"$BASE_URL/project/$PROJECT_ID/preview-environments/pr-42" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID" \
-H "Content-Type: application/json" \
-d '{
"image": "docker.io/library/nginx@sha256:6784fb0834aa7dbbe12e3d7471e69c290df3e6ba810dc38b34ae33d3c1c05f7d",
"ttl_hours": 24
}'The first acceptance normally returns 202 Accepted with id, key, operation_id, and the current state. An idempotent replay that already finished may return 200. If an active operation has an incompatible payload, the API returns 409 and you should query the existing operation.
Read a preview
GET /project/:projectId/preview-environments/:previewKeyReturns the current state, URL, plan, hourly BRL price when available, inherited environment state, timestamps, and current operation. It does not return environment variable values.
curl -sS \
"$BASE_URL/project/$PROJECT_ID/preview-environments/pr-42" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID"Remove a preview
DELETE /project/:projectId/preview-environments/:previewKeyRequests immediate removal of the preview and ends its billing when removal is confirmed. The operation is idempotent: repeating the delete does not create another environment or another charge.
curl -sS -X DELETE \
"$BASE_URL/project/$PROJECT_ID/preview-environments/pr-42" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID"The first request may return 202 Accepted with operation_id. When the preview is already removed, a repeat returns the terminal state or an equivalent success response. The Action does not require IMAGE for this path.
Operation polling
Read an operation
GET /project/:projectId/preview-environments/:previewKey/operations/:operationIdUse the operation_id returned by PUT or DELETE to follow the operation. Poll at bounded intervals and stop at a terminal state.
Possible states:
acceptedreservingprovisioningupdatingavailabledeletingdeletedfailed
An upsert succeeds at available; a delete succeeds at deleted. At failed, read the public code and correct the input before trying again.
curl -sS \
"$BASE_URL/project/$PROJECT_ID/preview-environments/pr-42/operations/operation-id" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "x-organization-id: $ORGANIZATION_ID"The operation response contains only state, public identifiers, timestamps, the URL when available, expires_at, and a sanitized public error. It does not contain secrets or internal details.
Public errors
Domain codes stay stable and messages are actionable:
| Code | Meaning |
|---|---|
preview_not_enabled | Opt-in is not enabled for the primary project. |
invalid_preview_key | The key does not meet the allowed format or size. |
preview_plan_not_allowed | The plan is not in the allowed list. |
preview_ttl_invalid or preview_ttl_exceeds_limit | Use a TTL between 1h and 168h and within the project limit. |
preview_organization_limit_reached or preview_project_limit_reached | The effective project or organization limit was reached. |
parent_configuration_unavailable | The primary project lacks enough public configuration to apply the preview. |
preview_operation_in_progress | An active operation already exists for the same identity. |
preview_unavailable | The preview could not become available with the provided input. |
preview_wait_timeout | The Action reached its maximum wait time. |
In addition to the code, the API may return a message and a request identifier for support. Never expose the full response in public logs when it is part of an automation.
HTTP status codes
200: successful read or replay of an operation that already finished.202: upsert or delete accepted for processing.400: invalid body, key, action, or duration.401: missing or invalid credential.403: insufficient organization, project, or permission; opt-in must also be active for API Key mutations.404: primary project, preview, or operation not found.409: conflict with an active operation or incompatible payload.