HTTP API

Errors

Response envelope, status codes, and how to handle each one.

Envelope

Every response, success or error, uses the same shape:

json
{
    "success": false,
    "code": 404,
    "message": "Product not found.",
    "error": { "code": "product.not_found" },
    "data": null,
    "_req": { "reqId": "019faa4c-6f6e-716a-a548-fc2ea447953b", "resTime": 6 }
}
FieldMeaning
successtrue for 2xx, false otherwise.
codeMirrors the HTTP status code.
messageHuman-readable description: safe to log, not always safe to show to end users.
errorMachine-readable failure detail on error, null on success. See machine-readable error codes.
dataResource payload on success, null on error.
_reqRequest id and server-side timing. See request IDs.

The HTTP status and the envelope code always agree. Branch on either.

Status codes

StatusMeaningWhen you'll see it
200OKSuccessful read or update.
201CreatedA resource was created.
400Bad RequestMalformed body, or invalid query parameters such as limit=101.
401UnauthorizedMissing, malformed, revoked, or non-member API key. Rotate the credential.
402Payment RequiredYour plan's limit is reached, or the resource is locked behind an upgrade.
403ForbiddenThe key is valid but lacks the required scope, project access, or product access. Do not rotate the key; widen its scopes.
404Not FoundResource doesn't exist, or your key can't see it.
409ConflictA slug or name is taken, a baseline drifted, or the action conflicts with current state.
405Method Not AllowedThe path exists but does not serve this method. The Allow header lists the methods it does serve, and error.code is request.method_not_allowed.
413Payload Too LargeThe request body is larger than 1 MiB (1,048,576 bytes). error.code is request.body_too_large, and message states the limit. Split large batches by size as well as by count.
429Too Many RequestsThe API-key routes are not rate limited, so there a 429 only comes from infrastructure in front of the API, such as a CDN or a proxy. The anonymous exposed and example feeds allow 600 requests a minute per client and answer 429 request.rate_limited with a Retry-After header beyond that. Retry with backoff.
500Internal Server ErrorUnexpected. Retry with backoff, then check status.envless.cloud.
503Service UnavailablePlanned maintenance. Every route except the maintenance status endpoint answers 503 while maintenance mode is on. Retry with backoff.

Telling maintenance apart from a fault

During maintenance every route returns 503 with the message Service is currently under maintenance. Please try again later.. One endpoint stays up and needs no authentication, so retry logic can tell a planned window apart from a real fault instead of treating both as a 500:

bash
curl https://api.envless.cloud/maintenance/status
json
{
    "success": true,
    "message": "OK",
    "data": { "maintenance": false }
}

Back off and keep retrying on 503, rather than failing the job. A 500 on the other hand is unexpected and worth surfacing.

404 vs 403

The API returns 404, not 403, when a resource exists but your key can't see it. This avoids leaking the existence of projects, environments, or variables across workspaces.

If you're sure the resource exists and your key should reach it, double-check the workspace association in the dashboard.

Handling errors

const res = await fetch('https://api.envless.cloud/projects/my-app', {
    headers: { Authorization: `Bearer ${token}` },
});
const body = await res.json();

if (!body.success) {
    switch (body.code) {
        case 401: throw new Error('Bad key, rotate it.');
        case 404: throw new Error('Project not found.');
        default:  throw new Error(`Envless: ${body.message}`);
    }
}

The SDKs do this unwrapping for you and raise a typed error carrying the same code, resource, field and request id: EnvlessApiError in TypeScript and in Python, Envless::ApiError in Ruby, and Envless\Exception\ApiException in PHP, where the code is errorCode.

Request IDs

Every response includes a _req field with the request ID and server-side timing:

json
{
    "success": true,
    "code": 200,
    "message": "OK",
    "error": null,
    "data": { /* ... */ },
    "_req": { "reqId": "019faa4c-6f6e-716a-a548-fc2ea447953b", "resTime": 14 }
}

The same id is returned in the Request-Id response header. Send your own Request-Id header and it will be echoed back, so you can correlate a call with your own logs. Include reqId when you contact support; it points us at the exact request in ours.

Machine-readable error codes

On failure the envelope carries an error object. Branch on error.code, never on message; messages are prose and will change.

json
{
    "success": false,
    "code": 403,
    "message": "auth.scope_missing: this key needs \"variables:write\".",
    "error": { "code": "auth.scope_missing", "resource": "variables:write" },
    "data": null,
    "_req": { "reqId": "019faa4c-6f6e-716a-a548-fc2ea447953b", "resTime": 2 }
}

error.resource names the scope or resource involved, and error.field names the offending request field when the failure is a validation error. Both keys are omitted when they don't apply, so only error.code is guaranteed. Every failure response carries a code; the full enumeration lives in the OpenAPI spec under components.schemas.ApiError.

PrefixMeaning
auth.*Credential, scope, or access problem.
request.*Something about the request itself: body, pagination, or a generic not-found or conflict when no resource-specific code applies.
plan.*A plan limit or upgrade wall. plan.upgrade_required is a 402 an upgrade fixes (a tier limit, a locked project, or version history and rollback on Free, where error.resource is version); plan.limit_reached is a 409 at the 1,000-variables-per-environment fair-use cap, which no plan raises. See Plans and billing.
workspace.*Workspace lookup, slug, limit, and deletion failures such as workspace.delete_has_projects.
domain.*Custom domain failures such as domain.not_configured or domain.attempt_limit.
product.*, project.*, environment.*, variable.*, version.*Resource-specific failures such as project.ambiguous_product or variable.baseline_drifted.
member.*, invite.*, role.*, key.*, webhook.*Team and integration failures.
server.*An unexpected failure on our side.