Variables

Create a variable

Creates one variable in an environment.

POST/projects/{slug}/environments/{environmentSlug}/variables

Creates 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>"
}'
Try itPOSThttps://api.envless.cloud/projects/{slug}/environments/{environmentSlug}/variables
Path parameters
Query parameters

Authorization

  • Authorizationstringrequiredheader

    Bearer token: an Envless API key (ev_sk_…), created in the dashboard or with POST /keys. The key is bound to one workspace; every request runs inside it.

Path parameters

  • slugstringrequired

    Project slug or project uniqueId.

  • environmentSlugstringrequired

    Environment slug. A uniqueId will not resolve.

Query parameters

  • productstring

    Product 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, so api_key conflicts with an existing API_KEY.

  • valuestringrequiredmin length 1max length 20000pattern ^v[12]:

    CIPHERTEXT. Must start with v1: or v2:. Encrypt client-side with the workspace key.

  • typeenum<string>

    Options: "string", "number", "boolean"

  • isSecretboolean

    Stored, but never returned by any public read.

    Default: true

  • visibilityenum<string>

    Options: "server", "client"

  • requiredboolean

    Default: false

  • defaultValuestring | nullmax length 20000pattern ^v[12]:

    CIPHERTEXT, same workspace key and same v1:/v2: prefix as value. Send null or omit for none.

Responses

201Created

Create a variable succeeded.

  • successbooleanrequired
  • codeintegerrequired

    The HTTP status of this response, repeated in the body.

  • messagestringrequired
  • errornullrequired
  • dataobjectrequired
    Show child attributesHide child attributes
    • idstringrequired
    • namestringrequired

      Returned with the casing it was stored with. Live variables in one environment are unique without regard to case.

    • valuestring | null

      Ciphertext. Decrypt client-side with the workspace key.

    • typeenum<string>required

      Options: "string", "number", "boolean"

    • visibilityenum<string>required

      Options: "server", "client"

    • requiredbooleanrequired
    • defaultValuestring | null

      Ciphertext, encrypted with the same workspace key as value.

    • environmentIdstringrequired
    • createdAtstringrequireddate-time
    • updatedAtstring | nulldate-time
  • _reqobject
    Show child attributesHide child attributes
    • reqIdstring
    • resTimeinteger | 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.

  • successbooleanrequired
  • codeintegerrequired

    The HTTP status of this response, repeated in the body.

  • messagestringrequired
  • errorobjectrequired

    Machine-readable failure detail. Null on success.

    Show child attributesHide child attributes
    • codeenum<string>required

      Stable 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"

    • resourcestring

      The resource or scope the failure relates to.

    • fieldstring

      The request field that caused the failure.

  • dataobject | nullrequired

    Null on most failures. A plan limit carries { limit, current }.

  • _reqobject
    Show child attributesHide child attributes
    • reqIdstring
    • resTimeinteger | 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 - the Authorization header is missing, or its value does not start with ev_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, or auth.product_restricted for 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.resource names 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 409 plan.limit_reached when 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 409 project.ambiguous_product when 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.