/workspace/domain/verifyRuns one verification pass against the DNS you published. It is safe to call repeatedly and it never throws for a domain that simply is not ready yet: a failed check returns 200 with the domain's new status and failureReason, not an error status. Branch on domain.status, not on the HTTP code.
Verification is two-staged. First the Envless ownership TXT record must resolve, which flips challengeVerifiedAt. Only then is the certificate requested and its own validation records appear in records, meaning a full setup normally needs at least two calls with a DNS publish between them.
Calls are debounced. Verifying inside the debounce window returns the current state untouched, with retryAfterSeconds telling you when the next real attempt is allowed. Attempts are also capped: once spent, remove the domain and claim it again.
Requires the workspace:write scope.
curl --request POST \
--url 'https://api.envless.cloud/workspace/domain/verify' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"reattempt": false
}'https://api.envless.cloud/workspace/domain/verifyResponse 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.
Request body
application/json · optional
reattemptbooleanSet true to force a fresh certificate attempt after a failure, instead of only re-reading the current state.
Default:
false
Responses
200OK
Verify the custom domain succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
domainobject | nullrequiredOne of:
object,null
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 200,
"message": "OK",
"error": null,
"data": {
"domain": {
"uniqueId": "<string>",
"hostname": "<string>",
"hostnameUnicode": "<string>",
"status": "<string>",
"records": [
{
"type": "<string>",
"name": "<string>",
"value": "<string>",
"note": "<string>"
}
],
"verificationErrors": [
"<string>"
],
"failureReason": "<string>",
"hint": "<string>",
"lastCheckedAt": "2026-05-30T10:14:22.000Z",
"retryAfterSeconds": 1,
"canReattempt": true,
"lockedByPlan": true,
"graceEndsAt": "2026-05-30T10:14:22.000Z",
"verifiedAt": "2026-05-30T10:14:22.000Z",
"createdAt": "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": 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.
403Forbidden
auth.scope_missing- the key lacks workspace:write.auth.workspace_two_factor_required- the workspace requires two-factor authentication and the key's owner has not turned it on.
404Not Found
domain.not_found - no custom domain has been claimed for this workspace.
409Conflict
domain.attempt_limit - the verification attempt limit is spent; remove the domain and claim it again.
413Payload Too Large
request.body_too_large - the request body is larger than 1 MiB (1,048,576 bytes).
500Internal Server Error
domain.not_configured- this deployment has no custom-domain provider configured.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.