/products/{slug}/access/{memberId}Grants one workspace member access to a product. Idempotent: re-granting an existing grant succeeds and still returns granted: true. Takes no request body - the two path segments are the whole input. {memberId} is a workspace member id (the id on GET /members and on GET /products/{slug}/access), not a user id; passing a user id returns 404 member.not_found. Granting on a public product is allowed and simply records the grant, which only starts mattering if the product is later switched to private. Returns 200, not 201, because the grant is upserted rather than created.
Requires the product-access:write scope.
curl --request PUT \
--url 'https://api.envless.cloud/products/<slug>/access/<memberId>' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx'https://api.envless.cloud/products/{slug}/access/{memberId}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
slugstringrequiredProduct slug, or the product's uniqueId. A UUID-shaped value is resolved as an id only.
memberIdstringrequiredWorkspace member id, the same id space as PATCH /members/{id}. Not a user id.
Responses
200OK
Grant product access succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
idstringrequiredWorkspace member id that was granted.
userobject | nullrequiredShow child attributesHide child attributes
idstringrequirednamestringrequiredemailstringrequiredavatarstring | nullrequired
grantedbooleanrequiredAlways true on success, including when the grant already existed.
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 200,
"message": "OK",
"error": null,
"data": {
"id": "<string>",
"user": {
"id": "<string>",
"name": "<string>",
"email": "<string>",
"avatar": "<string>"
},
"granted": true
},
"_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": 401,
"message": "Something went wrong.",
"error": {
"code": "auth.api_key_required",
"resource": "<string>",
"field": "<string>"
},
"data": {},
"_req": {
"reqId": "<string>",
"resTime": 1
}
}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
product.locked - the product sits beyond the plan's product allowance.
403Forbidden
auth.key_project_restricted- the key is scoped to a list of project ids; such keys cannot manage product access.auth.scope_missing- the key lacks product_access:write.auth.workspace_two_factor_required- the workspace requires two-factor authentication and the key's owner has not turned it on.
404Not Found
product.not_found- no product matches, or it is private and the key owner has no grant.member.not_found- no live member of this workspace has that member id - most often because a user id was passed instead.
409Conflict
product.not_found - the product was deleted while the grant was being created.
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.