Workspace

Plans and billing

What each plan allows, the exact quota per tier, what a locked product or project means, and how upgrading, invoices and the customer portal work.

A plan belongs to a workspace, not to a user and not to a product. It is a single field on the workspace row, plan, holding one of free, starter, business or enterprise, and every new workspace starts on free. Every quota below is read off that one field at request time, so a plan change takes effect on the next request with no rebuild, no re-sync and nothing to re-link.

Payments, checkout, receipts and the customer portal are handled by Polar. Envless stores the subscription id, the status and the current period, and applies the plan when Polar's webhook arrives. It never sees a card number.

The four plans

Tier keyName in the dashboardWho it is forMonthlyYearly
freeFreeFor testing and exploring$0$0
starterDevFor developers and small initiatives$5$50
businessTeamFor teams and business companies$25$250
enterpriseEnterpriseFor companies that need everything, unlimited$50$500

The yearly price is ten months of the monthly one, which the plan picker prints as a 17% saving next to the yearly figure. The picker is the same modal everywhere and shows all four plans side by side with a monthly and yearly toggle. The card for your current tier reads Current plan and any tier below it reads Below your plan, and both are disabled, so only the plans above yours are selectable. On a workspace with no subscription those buttons read Choose and start a checkout; on a workspace that already holds a subscription they read Switch plan and open the customer portal instead, because a subscribed workspace changes plan there rather than by buying again.

The tier keys are what the API answers with, and they do not match the dashboard names one to one: a workspace read over the API-key surface carries plan as free, starter, business or enterprise, never Dev or Team. starter is the Dev plan and business is the Team plan. The dashboard, the plan picker and the invoice list all print the display names, Free, Dev, Team and Enterprise.

What every plan includes

Nothing below is gated by tier. It is the same on Free as it is on Enterprise:

  • API, SDK and CLI. ev_sk_ machine keys, the public API, every SDK (@goenvless/sdk, the Python and Ruby envless-sdk, and the PHP envless/sdk) and @goenvless/cli work on every plan, and a plan change never turns a key off. The number of keys is not plan-scoped either.
  • Analytics, usage and two factor. Usage analytics, the usage dashboards and two factor authentication, including the workspace-wide requirement, are on every plan.
  • Full access to all tools. The raw editor, compare, import and export, virtual environments, webhooks and everything else in the dashboard.
  • Variables. Unlimited on every plan, subject to the fair-use cap described under the quota table.

Exact quota per tier

These are the numbers the API enforces. The Counted per column matters as much as the number, because three of these limits are not workspace-wide:

LimitFreeDevTeamEnterpriseCounted per
Products1315UnlimitedWorkspace
Projects1315UnlimitedWorkspace
Environments2315UnlimitedProject
VariablesUnlimitedUnlimitedUnlimitedUnlimitedEnvironment
Version historyLatest onlyFullFullFullEnvironment
Members, pending invites included1115UnlimitedWorkspace
Custom domainNone111Workspace
SupportNoneNone24/7Priority 24/7

Projects are counted across the whole workspace rather than per product, so three projects on Dev means three in total however you spread them over your three products. Environments are counted per project, so Free gives you two environments in each project and Dev gives you three in each of your three projects rather than three overall. Variables are counted per environment. Unlimited on Enterprise is a genuine absence of a ceiling: the API never refuses a create on that tier for a product, project, environment or member count.

Variables are unlimited on every plan, with one fair-use cap. The API stops creates at 1,000 variables in a single environment on every tier, Enterprise included, and answers You've reached the 1,000 variables limit on your plan. Contact support to raise it. past it. That cap is a fair-use guard rather than a tier limit, so no upgrade moves it: raising it for a workspace is a message to envless.cloud/contact and it is lifted per workspace.

The member limit counts active members and pending invites together, and a limit of 1 means the workspace owner and nobody else. Team members start on the Team plan: on Free and Dev the invite is refused with Your current plan does not include team members. Upgrade your plan to invite members. before an email is ever sent. Team seats 15 people and Enterprise has no seat count.

A custom domain is a paid-plan feature and every paid plan includes exactly one: a workspace holds a single hostname, and Dev, Team and Enterprise all grant that one domain, so no plan raises the count. Free has none. What a downgrade does to a domain you already hold is covered under Cancelling below.

Version history

Publishing works the same on every plan: every publish creates a version, versions are never deleted, and /versions/latest, the CLI, the SDKs and the exposed example feed all keep reading the newest one. What Free does not include is the history behind it.

OperationFreeDev, Team and Enterprise
Publish a versionAllowedAllowed
List versionsReturns the latest version only, with historyLimited: true, historyLimit: 1 and hiddenCount saying how many older ones existThe full list
Get a version and the variables it capturedThe latest version only. Any older id answers 402 plan.upgrade_required with error.resource versionAny version
Roll back to a versionRefused with 402 plan.upgrade_required and Rollback needs a paid plan. Upgrade to restore an earlier version.Allowed
Per-variable historyThe latest entry only, with the same historyLimited flagThe full trail

The message on a hidden version is Your plan keeps only the latest version. Upgrade to browse, compare or restore earlier versions. The one read that ignores the tier is the raw entry ciphertext route behind a passphrase rotation, which has to rewrite every stored version on every plan. Nothing is lost while you are on Free: every version published before a downgrade is still stored, and the moment the workspace is back on Dev or above the whole history is readable and restorable again.

Limits that are not part of any plan

These are fixed for every workspace and do not move when you upgrade:

LimitValueCounted per
Workspaces100User
Custom roles25Workspace
Versions stored1,000Environment

The versions row is a storage ceiling, not the history rule above: an environment holds at most 1,000 snapshots on every plan, and the 1,001st publish is refused with This environment has reached its limit of 1000 versions. whatever the tier.

When you hit a limit

What you see depends on which surface you were using, because the dashboard and the API-key surface answer a limit differently.

In the dashboard

Creating or duplicating a product, a project or an environment past the limit refuses with Plan limit reached., plus a payload naming the resource, the limit, the plan and your current count. The dashboard reads that payload and opens the plan picker on the spot, closing the create modal behind it.

Variables behave differently, because the dashboard always adds them through the bulk endpoint. That endpoint does not refuse the whole request: it creates however many still fit under the 1,000 fair-use cap, skips the rest with the reason plan limit reached, and answers success. The dashboard turns the skip into a Limit reached toast reading Some variables were not saved. Contact support to raise the limit., and no picker opens, because no plan raises that cap.

Hitting the member limit is a plain refusal with the message above and no picker. Custom domains are gated before you can even try: on Free the domain section renders a Custom domains are on paid plans banner with an Upgrade plan button in place of the claim form, so the API's own refusal, Custom domains need a paid plan., is what a direct API call gets rather than what the dashboard shows you. Opening an older version, or restoring one, on Free comes back as a plan refusal carrying the same limit payload, with the version messages above.

On the API-key surface

The public API distinguishes a tier limit you can buy your way past from the fair-use cap you cannot:

SituationStatuserror.codeMessage
Tier limit reached (products, projects, environments)402plan.upgrade_requiredYou've reached the <n> <resource> limit on your plan. Upgrade to add more.
Custom domain claimed on Free402plan.upgrade_requiredCustom domains need a paid plan.
Fair-use cap reached (1,000 variables in one environment)409plan.limit_reachedYou've reached the 1,000 <resource> limit on your plan. Contact support to raise it.
Older version read, or any rollback, on Free402plan.upgrade_required, error.resource versionYour plan keeps only the latest version. Upgrade to browse, compare or restore earlier versions. or Rollback needs a paid plan. Upgrade to restore an earlier version.
Invite past the member limit402member.seat_limitYour current plan does not include team members. Upgrade your plan to invite members. or You have reached the maximum of <n> members for your plan.
Write to a locked product (renaming it, or granting or revoking product access)402product.lockedThis product exceeds your plan limit. Upgrade to make changes.
Any request touching a locked project, reads included402plan.upgrade_requiredauth.project_locked: this project is locked because it exceeds your plan limit. Upgrade your plan to sync it.

plan.limit_reached is specifically the fair-use cap, and it is a 409, not a 402. Branch on 402 when you want "an upgrade would fix this" and on 409 with plan.limit_reached when you want "no plan fixes this, ask support". See Errors for the envelope these arrive in.

The CLI prints whatever the API sent, so a plan refusal reaches your terminal as the message above and nothing is pre-judged locally.

Locked products and projects

Downgrading, or losing a subscription, never deletes anything. Instead the resources beyond your new limit are locked. Nothing is hidden and nothing is destroyed: the rows stay, the ciphertext stays, and every version stays.

Which ones lock is decided by creation order. The API lists the workspace's live products (or projects) oldest first, keeps the first N where N is your plan's limit, and locks everything after that. So on a workspace that drops to Free with four products, the oldest product stays usable and the three newer ones lock, and a Team workspace with twelve projects that drops to Dev keeps its three oldest. Deleting a locked one frees nothing, because it was already past the line. Deleting an older, unlocked one is what pulls the next locked one back inside the limit.

The dashboard marks a locked product or project with a banner reading This product is locked or This project is locked, with It exceeds your current plan limit. Upgrade your plan to edit, sync, or export it. and an Upgrade plan button that opens the picker.

OperationLocked productLocked project
Read it in the dashboardAllowedAllowed
Rename it, change its slug or iconRefusedRefused
Create, update, delete or duplicate an environment inside itDecided by the project's lockRefused
Create, update, delete or bulk-apply a variable inside itDecided by the project's lockRefused
Publish a version, or roll one backDecided by the project's lockRefused
Delete it in the dashboardAllowedAllowed
Read it with an ev_sk_ API keyAllowed, the response carries locked: trueRefused
Write to it with an ev_sk_ API keyRenaming it and granting or revoking product access are refused; deleting it is not lock-checkedRefused, deleting it included

The two locks are computed separately, one from the workspace's product count and one from its project count, so they usually coincide but do not have to. Everything below the project (environments, variables, versions) is gated on the project's lock alone, which is why a locked product with an unlocked project underneath still accepts variable writes into it.

The row that catches people out is reading a locked project with a key. The dashboard still lets a signed-in user read that project's variables, but the API-key surface checks the lock before it serves anything, reads included, so a machine pulling with ENVLESS_TOKEN gets 402 rather than values. A locked project therefore breaks CI and any deployment that syncs at boot, while still looking readable to the person checking in the browser. See Keys and Production & CI.

There is no manual unlock. No dashboard toggle, no CLI command, no support flag, and no way to choose which product stays live. Locking is computed from your plan limit and creation order on every request, so the only two ways out are upgrading to a plan whose limit covers the resource, or deleting enough older resources that the locked one moves back inside the limit.

One asymmetry worth knowing if you are parsing responses rather than reading messages: a locked product answers 402 with product.locked, while a locked project answers 401 with auth.unauthorized and This project exceeds your plan limit. Upgrade to make changes. on the dashboard's own endpoints, and 402 with plan.upgrade_required and the auth.project_locked: message on the API-key surface. Neither the status nor the wording is shared between the two, so handle both rather than matching on either alone.

Upgrading

Open Billing

Go to Billing. The overview card names your current plan, a status badge (Active, Canceling or No subscription), your product and project counts against the plan limits (on a plan with no ceiling it prints the count alone), your member count and, once you are subscribed, the price, the billing period and the current period dates.

Pick a plan

Upgrade plan opens the picker with the monthly and yearly toggle. The same picker opens from the sidebar prompt, which the owner sees while the workspace has no subscription, from a locked product or project banner, and from anywhere a create was refused for a plan limit.

Pay on Polar

Choosing Dev, Team or Enterprise creates a Polar checkout session for that plan and interval and sends your browser to it. The workspace id travels with the session, which is how the subscription is attached to the right workspace afterwards.

The plan applies on the webhook

Polar posts a signed subscription.created event back to Envless. That handler stores the subscription and sets plan from the product that was bought, as long as that Polar product is one the deployment is configured with, a monthly and a yearly product per paid plan; an unrecognised one is logged and the plan is left where it was. Locked resources unlock on the next request, hidden version history becomes readable again, and a custom domain sitting in its grace window is restored.

Only the workspace owner can start a checkout or open the portal, because both need the billing:manage permission. That permission is deliberately unavailable to everyone else: admins do not hold it, and it is on the list of permissions a custom role can never be granted.

PermissionOwnerAdminViewerCustom roleCovers
billing:readYesYesYesGrantableSeeing the Billing tabs, the plan overview and the invoice list
billing:invoice_readYesYesYesGrantableViewing and downloading an invoice
billing:manageYesNoNoNeverCheckout and the customer portal

Checkout refuses to run twice. While the workspace still holds a subscription that has not been revoked, a canceling one included, a second checkout is rejected with 400 and This workspace already has a subscription. Go to the billing page to change your plan, update payment details or cancel it., so switching between Dev, Team and Enterprise is done in the customer portal, not by buying again. The plan picker knows this too: on a subscribed workspace its buttons read Switch plan and open the portal.

The customer portal

Once a workspace has a subscription, the button on the Billing overview changes from Upgrade plan to Manage plan, and it opens a Polar customer portal session for the signed-in user's own Polar customer rather than for the workspace. Since only the owner holds billing:manage, that is in practice whoever ran the checkout. The portal is where the payment method, the plan change and the cancellation live. There is no cancel button anywhere in the Envless dashboard.

If the portal cannot be opened the dashboard shows Portal not opened with The billing portal could not be opened. Please try again later! and stays where it is.

Cancelling, lapsing and downgrading

Cancelling and losing the plan are two separate events, and the gap between them is the rest of the period you already paid for:

EventWhat Envless doesWhat you keep
subscription.updatedStores the new status and period and re-reads the plan from the product, so a plan change made in the portal lands here. A change to a smaller plan applies that plan's limits on the next request. A status that is no longer live applies the revocation below rather than waiting for itEverything the new plan allows
subscription.canceledRecords the cancellation date. The badge flips to Canceling and the period row relabels itself from Next renewal to Access endsThe plan, until the period ends
subscription.revokedMarks the subscription deleted and sets the workspace back to freeFree plan quotas. Everything past them locks

None of the three is load bearing on its own. Once an hour Envless reconciles both sides against Polar, which is the only source of truth for what you are on: a subscription Polar has canceled or ended is closed and the workspace goes back to free, a subscription Polar still bills is adopted even if its subscription.created never arrived, a plan that disagrees with the product Polar charges for is rewritten to follow Polar, and a workspace sitting on a paid plan with nothing on Polar behind it is put back on free. So a cancellation or an upgrade made in the Polar dashboard still lands if the webhook for it was never enabled, was retried into failure, or was dropped.

A revoke is where the effects bite. Products, projects and environments beyond the Free limits lock as described above, which will fail CI for any locked project before anyone notices in the browser, and version history drops to the latest version. Your ev_sk_ keys are not touched: they keep authenticating on Free, so nothing has to be rotated, and the locks lift as soon as the plan is back.

Members beyond the new limit stay in the workspace; what stops is inviting anyone new until the count is back under the limit, and an invite that was sent before the drop is refused at acceptance with This workspace has reached its plan member limit.

Deleting a workspace cancels its subscription for you, immediately and with no refund for the rest of the period. There is no separate step and nothing to cancel in the portal first, and neither the workspace nor the subscription can be restored afterwards. The billing recipients are emailed a confirmation of both, which the Billing and plan switch silences like any other billing mail.

A custom domain is not removed on the day the plan drops. When the workspace lands on a plan with no domain allowance, the domain moves to the plan_grace status and keeps serving for 14 days, with an email on the day it locks, after a week, and two days before the end. Upgrading inside that window restores it with no DNS changes; once the window closes the domain is removed. See Custom domain.

Invoices

Billing → Invoices lists one row per Polar order for this workspace's subscription:

ColumnWhat it holds
PlanFree, Dev, Team or Enterprise, the display name of the tier that order paid for
PeriodThe subscription period that order covers, or - when the order carries none
PriceWhat was charged, with the discount code underneath when one applied, and -$<amount> refunded when part of it came back
DateWhen the order was created

The row menu holds View invoice and Download invoice, both needing billing:invoice_read. Either one asks the API for a fresh invoice URL. If Polar has not generated the invoice PDF yet, Envless asks it to and then polls for up to twelve attempts at 250ms, roughly three seconds, before giving up. When it gives up nothing opens, so if the menu appears to do nothing on a very recent order, wait a moment and click again.

The list is paginated at 15 rows by default and a page is capped at 100, so there is no "all rows" option here. Search is never sent to Polar: typing in it refetches the first 100 orders and matches them in the browser against the plan name, the price, the discount code, the refund line and the formatted dates, so anything past those 100 rows is not searched.

Invoices are looked up by Polar customer, and that customer is a user rather than the workspace. The list is built from the orders belonging to the signed-in user's Polar customer id, filtered to this workspace's subscription, and a single invoice is served only when the order's customer id matches yours. So a teammate who holds billing:invoice_read but did not buy the subscription sees an empty table, and a workspace with no subscription at all sees an empty table too. Whoever ran the checkout is the person who can pull the invoices.

Needing more

Enterprise has no ceiling on products, projects, environments or members, so the limits a purchase does not move are the single custom domain per workspace and the 1,000-variables-per-environment fair-use cap. Raising it, and anything else bespoke, is a conversation rather than a purchase: envless.cloud/contact is the route, which is also what the refusal message means by Contact support to raise it.