A typed client with nothing hidden behind it
Zero runtime dependencies, dual ESM and CommonJS, and 113 methods that cover every one of the 80 public API operations. A parity check in CI fails the build the moment that stops being true.
The @goenvless/sdk package is a typed TypeScript client for the Envless public API. It exposes thirteen resource namespaces, three pagination shapes on every collection, two error classes you can branch on without parsing messages, and the same client-side encryption primitives the dashboard uses, so you can encrypt a value locally before writing it through the API. It is a separate concern from @goenvless/env, the runtime package that reads variables into a running application: the SDK manages Envless, the runtime consumes it.
What ships in the package
Verified against the built bundle and the parity script, not the documentation.
- 113 / 80
- Every documented operation has a method with a matching operationId and declared scope. A sdk:parity script diffs the manifest against the OpenAPI document and exits non-zero on any drift, so the coverage claim is enforced rather than asserted.
- 0
- No dependencies key at all. The built bundle imports nothing external and touches no Node built-ins, using only fetch, crypto.subtle and base64 helpers. Dual ESM and CommonJS with separate type declarations, sideEffects false, Node 20 or newer.
- 13
- me, workspace, workspaces, products, projects, environments, variables, versions, members, invites, roles, keys and webhooks. Each collection ships list, iterate and listAll.
- #token
- The API key lives in an ECMAScript private field, so it never appears in Object.keys, Object.values or a JSON.stringify of the client. A test asserts it, which matters the first time someone logs a client instance.
- 408, 429, 5xx
- Retries fire on GET, HEAD, PUT and DELETE only, with exponential backoff of 2^attempt times 250ms capped at 30 seconds. POST and PATCH are never retried, so a create cannot be duplicated by a timeout.
- 300s
- verifyWebhookSignature implements Standard Webhooks: HMAC-SHA256 over id, timestamp and the raw payload, compared in constant time, accepting the multi-signature header format.
Methods covering API operations
Runtime dependencies
Resource namespaces
Private field, not a property
The only retried statuses
Default webhook tolerance
Using it
Initialise once or construct explicitly, then branch on typed error codes rather than on message strings.
- 1
Install and initialise
Call init once at startup and import the shared client anywhere, or construct explicit clients when you talk to more than one workspace. The constructor throws if no token is present, and createClient falls back to ENVLESS_TOKEN.
npm install @goenvless/sdk import { init, envless } from '@goenvless/sdk' init({ token: process.env.ENVLESS_TOKEN }) const identity = await envless.me.get() - 2
Page through collections three ways
list returns one page with a pagination envelope, iterate is an async generator that walks every page, and listAll collects them. Both walking shapes clamp the per-request limit to 100, which is also the API ceiling.
const page = await envless.projects.list({ limit: 50, offset: 0, search: 'api' }) for await (const project of envless.projects.iterate()) console.log(project.slug) const all = await envless.projects.listAll() - 3
Branch on codes, never on messages
EnvlessApiError carries the status, a stable machine-readable code, the resource, the field, a request id and a retry hint, plus convenience getters for the cases you actually handle. EnvlessNetworkError carries the original cause.
import { EnvlessApiError, EnvlessNetworkError } from '@goenvless/sdk' try { await envless.projects.create({ name: 'Billing', slug: 'billing' }) } catch (error) { if (error instanceof EnvlessApiError) { if (error.isScopeMissing) console.error('key needs scope:', error.resource) else if (error.needsUpgrade) console.error('plan limit:', error.message) else console.error(error.code, error.status, error.requestId) } else if (error instanceof EnvlessNetworkError) console.error('unreachable') else throw error } - 4
Encrypt before you write
The API refuses any value that is not already an envelope, so writing a variable through the SDK means encrypting it locally first with the same primitives the dashboard uses.
import { encryptValue, envless } from '@goenvless/sdk' const { workspaceId } = await envless.me.get() const value = await encryptValue( 'postgres://user:pass@db.internal:5432/app', process.env.ENVLESS_PASSPHRASE, workspaceId ) await envless.variables.create('api', 'production', { name: 'DATABASE_URL', value }) - 5
Verify webhooks against the raw body
Pass the exact bytes you received, not a re-serialised object, or the signature will not match. The helper checks the timestamp against the tolerance window and compares in constant time.
import { verifyWebhookSignature } from '@goenvless/sdk' const event = await verifyWebhookSignature({ payload: await readRawBody(req), headers: req.headers, secret: process.env.ENVLESS_WEBHOOK_SECRET, toleranceSeconds: 300 })
Read the details
Full method reference, transport behaviour, and the encryption helpers, page by page.
- SDK overview Installation, the client, and how the package is laid out.
- Errors Every error code, what raises it, and which ones are worth retrying.
- Pagination The envelope, the generator, and the limits the API enforces.
- Encryption helpers encryptValue, decryptValue, key derivation and passphrase rotation.
- Webhook verification Standard Webhooks headers, tolerance, and the raw-body requirement.
- CLI, runtime and SDK Which package to reach for, and why they are deliberately separate.
The SDK FAQ
Straight answers about how this works in practice.
The SDK manages Envless: it creates projects, writes variables, rotates keys and reads audit data through the public API with an API key. The env package consumes Envless: it loads the variables for one project and environment into a running application and exposes them as a typed, read-only proxy. An application typically uses env; a script, an internal tool or an integration uses the SDK.
It refuses to construct in one unless you pass dangerouslyAllowBrowser, and the flag is named that way on purpose. Beyond the obvious problem of shipping an API key to users, the API only echoes the dashboard origin in its CORS headers, so a browser request would be blocked anyway. Use the SDK from a server.
Because a timeout does not tell you whether the write landed. Retrying a create after a request that may have succeeded is how you end up with two of something. GET, HEAD, PUT and DELETE are idempotent, so they retry on 408, 429 and the 5xx family with exponential backoff; anything else is handed to you to decide about.
No, and that is intentional: automatic encryption would mean the SDK holding your passphrase for the lifetime of the client. It exposes encryptValue and decryptValue so encryption is an explicit step you control, and the API refuses any value that is not already an envelope, so a forgotten call fails loudly with variable.ciphertext_invalid rather than silently storing plaintext.
Use variables.apply with a baseline. You send the id, name and updatedAt of every variable you based your change on; the server takes an advisory lock on the environment, recomputes drift, and rejects the whole call with 409 variable.baseline_drifted naming what changed. That is compare-and-set for a whole environment, and it is what the dashboard raw editor uses.
TypeScript is the one that ships today, and it is a plain REST API underneath: bearer token, JSON, standard status codes, and an OpenAPI document you can generate a client from. The encryption is the part worth porting carefully, and the encryption reference page documents the exact parameters so an implementation in another language interoperates.
A key carries a map over eleven resources at three levels: none, read or write. Each route declares what it needs and the check is a rank comparison, so a miss returns 403 auth.scope_missing naming the exact scope required. GET /me is the only public route with no scope requirement. Workspace RBAC is a separate, finer catalogue of 44 named permissions that governs people rather than keys.
Explore the rest of Envless
Every capability, broken down the same way.
AI agents
Give a coding agent working secrets with no plaintext .env in the repository for it to read or commit.
TerminalThe CLI
24 commands, browser device login, local encryption, and JSON output on every list.
EncryptionEnd-to-end encryption
AES-256-GCM on your machine, a key the server never sees, and an API that structurally refuses plaintext.
HistoryVersioning and history
Numbered snapshots in one locked transaction, attributed per-variable history, and signed events for every change.
TeamsTeam access control
44 permissions, custom roles, private products isolated to named members, and keys clamped to their creator.
EnvironmentsEnvironment variables
Typed, per-environment variables with server/client splitting, .env import and export, and a real diff.
Public feedsVirtual environments
Serve an environment as an encrypted JSON feed at a public URL that needs no SDK, CLI or API key.
SetupGetting set up
Install, sign in, bind the directory, unlock, run. What each step writes, and what it does not.
CloningOne-click duplicate
Clone an environment, project or whole product with every variable, in one transaction, without decrypting anything.
Ship secrets, not chaos.
Start free today and discover why developers trust Envless for end-to-end encrypted, versioned secrets across every environment.