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.

What the SDK gives you

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

Methods covering API operations

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

Runtime dependencies

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

Resource namespaces

me, workspace, workspaces, products, projects, environments, variables, versions, members, invites, roles, keys and webhooks. Each collection ships list, iterate and listAll.
#token

Private field, not a property

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

The only retried statuses

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

Default webhook tolerance

verifyWebhookSignature implements Standard Webhooks: HMAC-SHA256 over id, timestamp and the raw payload, compared in constant time, accepting the multi-signature header format.

Using it

Initialise once or construct explicitly, then branch on typed error codes rather than on message strings.

  1. 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. 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. 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. 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. 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
    })

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.

Get Started

Ship secrets, not chaos.

Start free today and discover why developers trust Envless for end-to-end encrypted, versioned secrets across every environment.