Variables

Create variables in bulk

Creates up to 200 variables in one call.

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

Creates up to 200 variables in one call. This endpoint is partial success by design: an item the server refuses is reported in skipped and the rest are still created, so always read skipped and count rather than trusting the status. A batch in which every single item is refused does not come back as a 201, it fails with a 4xx and the per-item reasons.

An item is skipped, never fatal, when the name repeats inside the request, fails validation, collides with an existing live variable, or would push the environment past the 1,000 variables per environment fair-use cap that applies on every plan (reason: "plan limit reached"). A name that fails the ^[A-Za-z_][A-Za-z0-9_]*$ shape or the 1 to 100 character bound is reported the same way, with the validation message as its reason. Names are compared without regard to case throughout, so two items differing only in case collide with each other and api_key collides with a live API_KEY. Items are created in request order, so plan overflow drops the tail.

value and defaultValue are ciphertext, encrypted client-side with the workspace key, prefixed v1: or v2:. The prefix is checked for every item before anything is written: one plaintext value or defaultValue fails the whole request with 400 variable.ciphertext_invalid, and error.field names the field.

The request body may be at most 1 MiB (1,048,576 bytes); a larger one is refused with 413 request.body_too_large before any item is read. 200 variables with values near the 20,000 character cap do not fit, so split large batches by size as well as by count.

Requires the variables:write scope.

curl --request POST \
  --url 'https://api.envless.cloud/projects/<slug>/environments/<environmentSlug>/variables/bulk' \
  --header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx' \
  --header 'Content-Type: application/json' \
  --data '{
  "variables": [
    {
      "name": "SENTRY_DSN",
      "value": "v1:8Wm3kQ...ciphertext",
      "type": "string",
      "isSecret": true,
      "visibility": "server",
      "required": false,
      "defaultValue": "<string>"
    }
  ]
}'
Try itPOSThttps://api.envless.cloud/projects/{slug}/environments/{environmentSlug}/variables/bulk
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.

Query parameters

  • productstring

    Product slug, to disambiguate a project slug shared across products.

Request body

application/json · required

  • variablesobject[]requiredmin items 1max items 200
    Show child attributesHide child attributes
    • namestringrequiredmin length 1max length 100pattern ^[A-Za-z_][A-Za-z0-9_]*$

      Variable name. Same rule as the single create: ^[A-Za-z_][A-Za-z0-9_]*$, 1 to 100 characters, stored with the casing you send, unique in the environment without regard to case. A name that fails lands in skipped with the reason, it does not fail the whole batch.

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

      CIPHERTEXT. The prefix is NOT enforced server-side on this endpoint. Enforce it yourself.

    • typeenum<string>

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

    • isSecretboolean

      Default: true

    • visibilityenum<string>

      Options: "server", "client"

    • requiredboolean

      Default: false

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

      CIPHERTEXT, same key and prefix as value. Not prefix-checked here either.

Responses

201Created

Create variables in bulk succeeded.

  • successbooleanrequired
  • codeintegerrequired

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

  • messagestringrequired
  • errornullrequired
  • dataobjectrequired
    Show child attributesHide child attributes
    • createdobject[]required

      Only id and name. Re-read the variable if you need the full record.

      Show child attributesHide child attributes
      • idstringrequired
      • namestringrequired
    • skippedobject[]required
      Show child attributesHide child attributes
      • namestringrequired
      • reasonstringrequired

        One of: duplicate in request, already exists, plan limit reached, or a validation message such as the one for a name that fails ^[A-Za-z_][A-Za-z0-9_]*$.

    • countintegerrequired

      Length of created. A 201 always created at least one variable, everything the server refused is in skipped.

  • _reqobject
    Show child attributesHide child attributes
    • reqIdstring
    • resTimeinteger | null
{
    "success": true,
    "code": 201,
    "message": "OK",
    "error": null,
    "data": {
        "created": [
            {
                "id": "<string>",
                "name": "STRIPE_SECRET_KEY"
            }
        ],
        "skipped": [
            {
                "name": "STRIPE_SECRET_KEY",
                "reason": "<string>"
            }
        ],
        "count": 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.

  • 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

  • request.invalid - the body is not JSON, variables is missing, empty or longer than 200, or an item lacks a string name or string value; message says which.
  • request.body_invalid - every item was refused; data carries created, count and the per-item skipped reasons.
  • variable.ciphertext_invalid - an item's value or defaultValue does not start with v1: or v2:; nothing is written and error.field names the field.

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 by the plan's project limit. The variable cap itself never surfaces here, it appears per item in skipped.

403Forbidden

  • auth.key_project_restricted - the key's project allowlist excludes this project, or auth.product_restricted for 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.
  • auth.scope_missing - the key lacks a scope this operation requires; error.resource names it.

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.

413Payload Too Large

request.body_too_large - the request body is larger than 1 MiB (1,048,576 bytes).

500Internal Server Error

server.error - the insert failed, for example because the environment was deleted mid-request; items refused before the insert are reported in skipped with a 201 instead.

503Service Unavailable

server.error - the API is in maintenance mode or is missing its configuration; retry with backoff.