/projects/{slug}/environments/{environmentSlug}/variables/bulkCreates 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>"
}
]
}'https://api.envless.cloud/projects/{slug}/environments/{environmentSlug}/variables/bulkResponse 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.
Query parameters
productstringProduct slug, to disambiguate a project slug shared across products.
Request body
application/json · required
variablesobject[]requiredmin items 1max items 200Show 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 inskippedwith 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"isSecretbooleanDefault:
truevisibilityenum<string>Options:
"server","client"requiredbooleanDefault:
falsedefaultValuestring | nullmax length 20000pattern ^v[12]:CIPHERTEXT, same key and prefix as
value. Not prefix-checked here either.
Responses
201Created
Create variables in bulk succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
createdobject[]requiredOnly id and name. Re-read the variable if you need the full record.
Show child attributesHide child attributes
idstringrequirednamestringrequired
skippedobject[]requiredShow child attributesHide child attributes
namestringrequiredreasonstringrequiredOne 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_]*$.
countintegerrequiredLength of
created. A 201 always created at least one variable, everything the server refused is inskipped.
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | 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.
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- the body is not JSON,variablesis missing, empty or longer than 200, or an item lacks a stringnameor stringvalue;messagesays which.request.body_invalid- every item was refused;datacarriescreated,countand the per-itemskippedreasons.variable.ciphertext_invalid- an item'svalueordefaultValuedoes not start withv1:orv2:; nothing is written anderror.fieldnames the field.
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. 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, orauth.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.auth.scope_missing- the key lacks a scope this operation requires;error.resourcenames 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.