/projects/{slug}/environments/{environmentSlug}/versionsLists the snapshots taken for an environment, newest first. Each row is metadata only, no variables and no ciphertext. Fetch GET /versions/{id} for the contents of one snapshot.
label is assigned server-side as v1, v2 and so on, derived from how many versions the environment already has. It is not guaranteed unique forever, since the counter is based on a count, so treat id as the identity.
createdBy falls back to the name and email captured at snapshot time when the user record has since been removed, and is null when neither is known.
Plan gate. Free workspaces see only the latest version: the list holds one row, historyLimited is true and hiddenCount tells you how many older versions exist. Reading, comparing or rolling back to any older version returns 402 plan.upgrade_required with error.resource: "version". Dev, Team and Enterprise see the full history. Publishing keeps working on every plan and nothing is deleted on a downgrade, the hidden versions come back on upgrade.
Requires the versions:read scope.
curl --request GET \
--url 'https://api.envless.cloud/projects/<slug>/environments/<environmentSlug>/versions' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx'https://api.envless.cloud/projects/{slug}/environments/{environmentSlug}/versionsResponse headers
Authorization
AuthorizationstringrequiredheaderBearer token: an Envless API key (
ev_sk_…), created in the dashboard or withPOST /keys. The key is bound to one workspace; every request runs inside it.
Path parameters
slugstringrequiredProject slug or project uniqueId.
environmentSlugstringrequiredEnvironment slug. A uniqueId will not resolve.
Query parameters
productstringProduct slug, to disambiguate a project slug shared across products.
limitintegermin 1max 100Page size, 1 to 100.
Default:
25offsetintegermin 0Rows to skip.
Default:
0
Responses
200OK
List versions succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
versionsobject[]requiredShow child attributesHide child attributes
idstringrequiredlabelstringrequirednotestring | nullvariableCountintegerrequiredcreatedByobject | nullShow child attributesHide child attributes
idstring | nullnamestring | nullemailstring | nullkeyobject | nullSet when the version was published by an API key. The id/name/email fields identify the key owner, not the actor.
Show child attributesHide child attributes
idstring | nullnamestring | nullprefixstring | null
createdAtstringrequireddate-timeenvironmentIdstringrequired
paginationobjectrequiredShow child attributesHide child attributes
limitintegerrequiredoffsetintegerrequiredtotalCountintegerrequiredhasMorebooleanrequired
historyLimitedbooleanrequiredTrue when the plan hides part of the history. Free workspaces see only the latest version; Dev, Team and Enterprise see every version.
historyLimitintegerrequiredHow many versions the plan makes visible. 1 on Free, a very large sentinel on unlimited plans.
hiddenCountintegerrequiredHow many older versions exist but are hidden by the plan. 0 when nothing is hidden. Nothing is deleted on a downgrade, the hidden versions come back on upgrade.
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 200,
"message": "OK",
"error": null,
"data": {
"versions": [
{
"id": "<string>",
"label": "<string>",
"note": "<string>",
"variableCount": 1,
"createdBy": {
"id": "<string>",
"name": "<string>",
"email": "<string>",
"key": {
"id": "<string>",
"name": "<string>",
"prefix": "<string>"
}
},
"createdAt": "2026-05-30T10:14:22.000Z",
"environmentId": "<string>"
}
],
"pagination": {
"limit": 1,
"offset": 1,
"totalCount": 1,
"hasMore": true
},
"historyLimited": true,
"historyLimit": 1,
"hiddenCount": 1
},
"_req": {
"reqId": "<string>",
"resTime": 1
}
}Error envelope
Every failure below returns the same shape with success: false; branch on error.code, never on message.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrorobjectrequiredMachine-readable failure detail. Null on success.
Show child attributesHide child attributes
codeenum<string>requiredStable error code. Branch on this, never on
message.Options:
"auth.api_key_required","auth.api_key_not_found","auth.api_key_user_missing","auth.api_key_owner_not_member","auth.scope_missing","auth.scope_ceiling_exceeded","auth.key_project_restricted","auth.product_restricted","auth.permission_denied","auth.self_mutation_forbidden","auth.step_up_required","auth.step_up_fresh_required","auth.cli_token_not_accepted","auth.two_factor_required","auth.workspace_two_factor_required","auth.two_factor_invalid","auth.two_factor_replayed","auth.two_factor_locked","auth.two_factor_not_enabled","auth.two_factor_already_enabled","request.body_must_be_json","request.body_invalid","request.body_too_large","request.rate_limited","request.invalid_pagination","workspace.not_found","workspace.slug_conflict","workspace.limit_reached","workspace.delete_last","workspace.delete_too_early","workspace.delete_has_projects","domain.not_configured","domain.not_found","domain.invalid","domain.taken","domain.attempt_limit","product.not_found","product.slug_conflict","product.locked","project.not_found","project.ambiguous_product","project.slug_conflict","environment.not_found","environment.slug_conflict","environment.expose_disabled","environment.expose_private_product","variable.not_found","variable.name_conflict","variable.ciphertext_invalid","variable.baseline_drifted","version.not_found","member.not_found","member.already_exists","member.self_invite","member.self_mutation","member.owner_immutable","member.owner_cannot_leave","member.seat_limit","invite.already_pending","role.owner_not_assignable","role.limit_reached","role.immutable","role.in_use","invite.not_found","invite.expired","role.not_found","role.name_conflict","key.not_found","key.limit_reached","webhook.not_found","webhook.delivery_not_found","plan.limit_reached","plan.upgrade_required","request.not_found","request.method_not_allowed","request.conflict","auth.unauthorized","auth.forbidden","request.invalid","server.error"resourcestringThe resource or scope the failure relates to.
fieldstringThe request field that caused the failure.
dataobject | nullrequiredNull on most failures. A plan limit carries
{ limit, current }._reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": false,
"code": 400,
"message": "Something went wrong.",
"error": {
"code": "auth.api_key_required",
"resource": "<string>",
"field": "<string>"
},
"data": {},
"_req": {
"reqId": "<string>",
"resTime": 1
}
}400Bad Request
request.invalid_pagination - limit is outside 1 to 100 or either pagination value is not a whole number.
401Unauthorized
auth.api_key_required- theAuthorizationheader is missing, or its value does not start withev_sk_.auth.api_key_not_found- the key is unknown, inactive or revoked.auth.api_key_user_missing- the key is valid but its owner no longer exists.auth.api_key_owner_not_member- the key's owner is no longer a member of the key's workspace.
402Payment Required
plan.upgrade_required - the project is locked by the plan's project limit. Free workspaces are not refused here, they simply receive only the latest version with historyLimited: true.
403Forbidden
auth.scope_missing- the key lacksversions:read. This route also fails withauth.key_project_restrictedwhen the key's project allowlist excludes this project, andauth.product_restrictedfor an inaccessible private product.auth.workspace_two_factor_required- the workspace requires two-factor authentication and the key's owner has not turned it on.
404Not Found
project.not_found- no project with that slug in the workspace.request.not_found- the environment slug does not exist in the project.
409Conflict
project.ambiguous_product - the project slug exists under more than one product.
500Internal Server Error
server.error - an unexpected error; retry with backoff.
503Service Unavailable
server.error - the API is in maintenance mode or is missing its configuration; retry with backoff.
On the Free plan the list holds only the latest version and carries historyLimited: true, historyLimit: 1 and hiddenCount, the number of older versions that exist but are not listed. On Dev and above historyLimited is false and the full history is returned. Older versions and rollback need Dev or above, and reading one on Free is 402 plan.upgrade_required. See Plans and billing.