/keys/{id}Updates a key's label, enabled state, scopes or project fence. Send only the fields you want to change.
scopes merges, it does not replace. The map you send is layered on top of the key's stored scopes, so omitting a resource leaves it exactly as it was. To take a permission away you must explicitly send that resource with the level none - {"scopes": {"variables": "none"}}. Sending {"scopes": {}} changes nothing. projectIds, by contrast, replaces wholesale: whatever array you send becomes the fence, and [] removes the fence entirely.
The same two reductions as POST /keys apply to the merged result: the ceiling check first (403 auth.scope_ceiling_exceeded if you ask for more than the calling key holds), then a silent clamp down to the calling key owner's workspace role. Read data.scopes back off the response.
A key cannot re-scope, re-fence or disable itself. If id is the key making the request and the body contains scopes, projectIds, or active: false, the request fails with 409 auth.self_mutation_forbidden. Renaming yourself is allowed, and so is active: true on yourself, because neither can lock you out. Use a second key, or the dashboard.
Ordering gotcha: the scope-ceiling and project-subset checks run before the key is looked up, so a request with an over-privileged scopes map against an id that does not exist returns 403, not 404. Fix the scopes first, then trust the 404.
Unrecognised field types are ignored rather than rejected: a non-string name or a non-boolean active is dropped silently. A PATCH that ends up touching nothing succeeds with 200 and the unchanged key.
Keys are plan-independent: the API, SDK and CLI are included on every plan, including Free, so a plan change never switches a key off and active: true works on every tier.
There is no way to change a key's secret here. Rotation is not exposed - create a replacement key and delete this one.
Requires the keys:write scope.
curl --request PATCH \
--url 'https://api.envless.cloud/keys/<id>' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "Billing",
"active": true,
"scopes": {
"variables": "read",
"versions": "read"
},
"projectIds": []
}'https://api.envless.cloud/keys/{id}Response 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
idstringrequireduuidThe key's
id(UUIDv7). Not theprefix, not the secret; keys have no slug form.
Request body
application/json · required
namestringmin length 4max length 34New label. 4 to 34 characters. Non-string values are ignored.
activebooleanfalsedisables the key immediately; every subsequent request with it gets 401.truere-enables it. A key may not sendfalsefor itself. Non-boolean values are ignored.scopesobjectPartial scope map, MERGED onto the key's current scopes. Omitted resources are left untouched - send an explicit
noneto revoke. Unknown resource names are rejected with 400.Keys:
workspace,products,product-access,projects,environments,variables,versions,members,roles,keys,webhooks· values enum<string>Options:
"none","read","write"projectIdsstring[]REPLACES the project fence with exactly this set of project
idvalues (UUIDv7).[]removes the fence and makes the key workspace-wide.
Responses
200OK
Update an API key succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
idstringrequirednamestringrequiredprefixstring | nullThe non-secret leading segment, for identifying a key in a list.
scopesobjectrequiredPer-resource access level for an API key.
Keys:
workspace,products,product-access,projects,environments,variables,versions,members,roles,keys,webhooks· values enum<string>Options:
"none","read","write"projectIdsstring[]requiredProjects this key is fenced to. An empty array means unrestricted.
activebooleanrequiredlastUsedAtstring | nulldate-timekeyRollsintegercreatedAtstringrequireddate-timelastRolledAtstring | nulldate-time
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 200,
"message": "OK",
"error": null,
"data": {
"id": "<string>",
"name": "<string>",
"prefix": "<string>",
"scopes": {
"workspace": "none",
"products": "none"
},
"projectIds": [
"<string>"
],
"active": true,
"lastUsedAt": "2026-05-30T10:14:22.000Z",
"keyRolls": 1,
"createdAt": "2026-05-30T10:14:22.000Z",
"lastRolledAt": "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 is not parseable JSON.request.body_invalid-scopesnames an unknown resource or uses a level other than none/read/write,projectIdsis not an array of UUIDv7 project ids, ornameis outside 4-34 characters.
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.
403Forbidden
auth.scope_ceiling_exceeded- any requestedresource:leveloutranks what the calling key itself holds; checked before the key is looked up, so this can mask a 404.auth.key_project_restricted- the calling key is fenced to projects andprojectIdsis missing, empty, or contains a project outside that fence.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
key.not_found - no live key with that id exists in this workspace.
409Conflict
auth.self_mutation_forbidden - the key is patching itself with scopes, projectIds, or active: false.
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.