/projects/{slug}/environmentsCreates an environment in a project. Both name and slug are required; the slug is slugified server-side before validation and must be unique within the project. Project slugs are unique per product, so pass ?product=<productSlug> when the project slug is ambiguous - note the body is parsed and validated before the project is resolved, so a malformed body returns 400 even when the project reference is also wrong. The environment allowance is counted per project (Free 2, Dev 3, Team 15, Enterprise unlimited), not per workspace like products and projects, and is checked before the slug conflict. exposed cannot be set over the API and always comes back false on this response even though the stored value may differ later.
Requires the environments:write scope.
curl --request POST \
--url 'https://api.envless.cloud/projects/<slug>/environments' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "Billing",
"slug": "billing"
}'https://api.envless.cloud/projects/{slug}/environmentsResponse 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 the project's uniqueId. A UUID-shaped value is resolved as an id first, then as a slug.
Query parameters
productstringProduct slug used to disambiguate the project slug. Required whenever the same project slug exists in more than one product.
Request body
application/json · required
namestringrequiredmin length 2max length 50Display name. Required.
slugstringrequiredmin length 2max length 50pattern ^[a-z0-9]+(?:-[a-z0-9]+)*$Required. Unique within the project. Slugified server-side before validation.
Responses
201Created
Create an environment succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
idstringrequirednamestringrequiredslugstringrequiredprojectIdstringrequiredexposedbooleanrequiredWhen true every published version is readable without an API key. Read-only here; change it in the dashboard.
createdAtstringrequireddate-timeupdatedAtstring | nulldate-time
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 201,
"message": "OK",
"error": null,
"data": {
"id": "<string>",
"name": "<string>",
"slug": "<string>",
"projectId": "<string>",
"exposed": true,
"createdAt": "2026-05-30T10:14:22.000Z",
"updatedAt": "2026-05-30T10:14:22.000Z"
},
"_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.body_must_be_json- the body is absent or not parseable as JSON.request.body_invalid-nameorslugis missing or not a string, or either fails length or slug-pattern validation.
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 already holds its plan's environment allowance (Free 2, Dev 3, Team 15 per project; Enterprise is unlimited and never hits this). data carries { limit, current }.
403Forbidden
auth.scope_missing- the key lacks environments:write.auth.key_project_restricted- the project is not in the key's project allowlist.auth.product_restricted- the project's product is private and the key owner holds no grant on it.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 matches in scope for this key.
409Conflict
project.ambiguous_product- the project slug resolves to projects in more than one product; retry with?product=<productSlug>.plan.limit_reached- reserved for a plan whose environment allowance sits at or above the 1,000 fair-use cap. No current tier can trigger it: Free, Dev and Team fail with 402plan.upgrade_requiredat their own allowance instead, and Enterprise is unlimited and never hits a plan limit.datacarries{ limit, current }.environment.slug_conflict- the resolved slug is already taken inside the project.project.not_found- the project was deleted while the environment was being created.
413Payload Too Large
request.body_too_large - the request body is larger than 1 MiB (1,048,576 bytes).
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.