Products

List products

Returns the products in the workspace the key's owner can see.

GET/products

Returns the products in the workspace the key's owner can see. Products the owner cannot reach (private products with no grant, and no products.manage_private permission) are filtered out entirely rather than returned as stubs. Each row carries a locked flag: products beyond the plan's product allowance (Free 1, Dev 3, Team 15, Enterprise unlimited) are ordered by internal insertion order and everything past the cap is locked. Locked products still list and still read, but PATCH on them fails with 402 product.locked. Pagination is offset-based and applied in memory after the access filter, so pagination.totalCount is the count the caller can actually see, not the raw workspace total. search matches name and slug; order defaults to name and sorts A to Z, with asc and desc sorting oldest and newest first on createdAt.

Requires the products:read scope.

curl --request GET \
  --url 'https://api.envless.cloud/products' \
  --header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx'
Try itGEThttps://api.envless.cloud/products
Query parameters

Authorization

  • Authorizationstringrequiredheader

    Bearer token: an Envless API key (ev_sk_…), created in the dashboard or with POST /keys. The key is bound to one workspace; every request runs inside it.

Query parameters

  • limitintegermin 1max 100

    Rows per page. Must be a whole number; anything outside 1-100 is rejected with 400 request.invalid_pagination.

    Default: 25

  • offsetintegermin 0

    Rows to skip. Must be a whole number of 0 or greater.

    Default: 0

  • searchstring

    Case-insensitive substring match against product name and slug.

  • orderenum<string>

    name sorts A to Z, asc and desc sort oldest and newest first on createdAt. Any other value, including omitting it, sorts by name.

    Options: "name", "asc", "desc"

Responses

200OK

List products succeeded.

  • successbooleanrequired
  • codeintegerrequired

    The HTTP status of this response, repeated in the body.

  • messagestringrequired
  • errornullrequired
  • dataobjectrequired
    Show child attributesHide child attributes
    • productsobject[]required
      Show child attributesHide child attributes
      • idstringrequired
      • namestringrequired
      • slugstringrequired
      • iconstring | null
      • isPrivatebooleanrequired
      • lockedboolean

        True when the product exceeds the plan limit and is read-only until you upgrade.

      • createdAtstringrequireddate-time
      • updatedAtstring | nulldate-time
    • paginationobjectrequired
      Show child attributesHide child attributes
      • limitintegerrequired
      • offsetintegerrequired
      • totalCountintegerrequired
      • hasMorebooleanrequired
  • _reqobject
    Show child attributesHide child attributes
    • reqIdstring
    • resTimeinteger | null
{
    "success": true,
    "code": 200,
    "message": "OK",
    "error": null,
    "data": {
        "products": [
            {
                "id": "<string>",
                "name": "<string>",
                "slug": "<string>",
                "icon": "<string>",
                "isPrivate": true,
                "locked": true,
                "createdAt": "2026-05-30T10:14:22.000Z",
                "updatedAt": "2026-05-30T10:14:22.000Z"
            }
        ],
        "pagination": {
            "limit": 1,
            "offset": 1,
            "totalCount": 1,
            "hasMore": 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.

  • successbooleanrequired
  • codeintegerrequired

    The HTTP status of this response, repeated in the body.

  • messagestringrequired
  • errorobjectrequired

    Machine-readable failure detail. Null on success.

    Show child attributesHide child attributes
    • codeenum<string>required

      Stable 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"

    • resourcestring

      The resource or scope the failure relates to.

    • fieldstring

      The request field that caused the failure.

  • dataobject | nullrequired

    Null on most failures. A plan limit carries { limit, current }.

  • _reqobject
    Show child attributesHide child attributes
    • reqIdstring
    • resTimeinteger | 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_pagination - limit or offset is not a whole number, or limit falls outside 1-100.

401Unauthorized

  • auth.api_key_required - the Authorization header is missing, or its value does not start with ev_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 products:read.
  • auth.workspace_two_factor_required - the workspace requires two-factor authentication and the key's owner has not turned it on.

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.