/projects/{slug}/environments/{environmentSlug}/variablesCreates one variable in an environment.
value must be ciphertext. The server never sees plaintext, holds no key, and will not encrypt for you. Encrypt with the workspace key first, then send the result. Anything that does not start with v1: or v2: is rejected with 400 variable.ciphertext_invalid and error.field: "value".
defaultValue is also ciphertext, encrypted with the same workspace key and carrying the same v1:/v2: prefix. It is validated identically when present. Sending a plaintext defaultValue past any weaker path stores permanent plaintext that then propagates into every version snapshot taken afterwards, and into every rollback made from those snapshots.
Ciphertext is validated before the project and environment are resolved, so a malformed value against a project that does not exist returns 400, not 404.
isSecret is accepted and stored but is not part of the response body.
Requires the variables:write scope.
curl --request POST \
--url 'https://api.envless.cloud/projects/<slug>/environments/<environmentSlug>/variables' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "STRIPE_SECRET_KEY",
"value": "v1:8Wm3kQ...ciphertext",
"type": "string",
"isSecret": true,
"visibility": "server",
"required": false,
"defaultValue": "<string>"
}'https://api.envless.cloud/projects/{slug}/environments/{environmentSlug}/variablesResponse 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.
Request body
application/json · required
namestringrequiredmin length 1max length 100pattern ^[A-Za-z_][A-Za-z0-9_]*$Variable name. Must match
^[A-Za-z_][A-Za-z0-9_]*$and be 1 to 100 characters, the same rule the bulk and apply endpoints enforce. Lowercase is legal, and the name is stored and returned with exactly the casing you send, nothing upper-cases it. Uniqueness among the live variables of the environment is judged without regard to case, soapi_keyconflicts with an existingAPI_KEY.valuestringrequiredmin length 1max length 20000pattern ^v[12]:CIPHERTEXT. Must start with
v1:orv2:. Encrypt client-side with the workspace key.typeenum<string>Options:
"string","number","boolean"isSecretbooleanStored, but never returned by any public read.
Default:
truevisibilityenum<string>Options:
"server","client"requiredbooleanDefault:
falsedefaultValuestring | nullmax length 20000pattern ^v[12]:CIPHERTEXT, same workspace key and same
v1:/v2:prefix asvalue. Send null or omit for none.
Responses
201Created
Create a variable succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
idstringrequirednamestringrequiredReturned with the casing it was stored with. Live variables in one environment are unique without regard to case.
valuestring | nullCiphertext. Decrypt client-side with the workspace key.
typeenum<string>requiredOptions:
"string","number","boolean"visibilityenum<string>requiredOptions:
"server","client"requiredbooleanrequireddefaultValuestring | nullCiphertext, encrypted with the same workspace key as
value.environmentIdstringrequiredcreatedAtstringrequireddate-timeupdatedAtstring | nulldate-time
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 201,
"message": "OK",
"error": null,
"data": {
"id": "<string>",
"name": "<string>",
"value": "<string>",
"type": "string",
"visibility": "server",
"required": true,
"defaultValue": "<string>",
"environmentId": "<string>",
"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
variable.ciphertext_invalid - value or defaultValue does not start with v1: or v2:, with error.field naming the offender. Also 400 for request.body_must_be_json, request.body_invalid when name or value is missing or not a string, and request.body_invalid when the service rejects the name, value length (1 to 20000), type, visibility, required flag or an oversized defaultValue.
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 because the workspace exceeds its plan's project limit.
403Forbidden
auth.key_project_restricted- the key's project allowlist excludes this project, orauth.product_restrictedfor a private product the key owner cannot see.auth.workspace_two_factor_required- the workspace requires two-factor authentication and the key's owner has not turned it on.auth.scope_missing- the key lacks a scope this operation requires;error.resourcenames it.
404Not Found
project.not_found - the project does not exist, or environment.not_found for an unknown environment slug.
409Conflict
variable.name_conflict- a live variable of that name already exists in the environment, compared without regard to case. Also 409plan.limit_reachedwhen the environment is already at the 1,000 variables per environment fair-use cap. Variables are unlimited on every plan, but the same 1,000 per environment fair-use cap applies to every tier, so this path returns 409, not 402, and upgrading does not lift it. Also 409project.ambiguous_productwhen the project slug is not unique.environment.not_found- the environment was deleted while the variable was being created, which a cascading product or project delete can do mid-request.
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.