Skip to main content
The workser CLI operates your app from the outside. @workser/app runs inside it, on every request.

Zero configuration

Workser provisions the project’s credentials into the app’s environment, so there is nothing to wire up and no key for anyone to paste:
Outside a Workser-provisioned environment — a script, a test — configure it explicitly:
The default workser export is lazy — it’s constructed on first property access, so importing the module can never throw. A missing WORKSER_PROJECT_ID surfaces at the call that needed it, naming the variable, instead of as an opaque module-load crash in a serverless cold start.
Node 18+ (or Bun, Deno, or any runtime with fetch). Zero runtime dependencies.

What you get

These are the same records the Workser dashboard and your AI employees read and write. An app built on Workser isn’t integrating with a separate system — it’s reading its own business.

workser.business

Every resource has the same five methods:
Named resources: orders, sales, fulfillments, pages, carts. For anything this version doesn’t name yet, use the escape hatch rather than hand-rolling a fetch:
  • remove is a real delete with no undo. For anything user-facing, prefer a status change via update.
  • list filters are pass-through beyond limit/offset — check what the resource actually supports rather than assuming a filter applied.

workser.db

Two ways in, and the choice matters:
Parameterised SQL only. Parameters are sent separately and bound server-side, and there is deliberately no string-concatenation API — this is the single most likely place for a generated app to acquire an injection vulnerability.
The connection URI is a credential: never return it to a browser, log it, or put it in an agent’s context. A minted Data API token can only ever assume the project’s least-privilege workser_app_<projectId> role — never the owner — so it structurally cannot drop the database.

workser.storage

Uploading — pick the right one.
The client PUTs straight to storage, so a big file never occupies a request slot or a timeout budget.

workser.auth

Read-oriented on purpose:
Sign-in, sessions, password handling and OAuth belong to the app’s own auth setup, running against the project’s database. What this namespace answers is “who are my users” and “is auth switched on”. A user list is personal data — don’t log it, paste it into a conversation, or hand it to a third-party tool unasked.

Connected apps

The connection is owned by the project, not the app. An owner connects Gmail once and every app in the project can send mail — no OAuth flow to implement, no tokens to store, no refresh logic.
Order of operations: check the active connection → if needed, connect(toolkit) returns a redirect_url the owner opens → read tools(slug) for the current argument schema → execute the authorized action.
safeRun checks connection readiness. It does not grant end-user permission or open a desktop approval prompt. Your server must authenticate the user, authorize the business operation and validate the arguments. App connections covers both development-time use by agents and runtime use by your app.
run() sends someone’s real email, posts to their real Slack, charges a real card. Always pass an idempotencyKey keyed on the thing that must happen once — an order id, a message id — not a fresh UUID.disconnect(connectionId) removes the connection for the whole project, not just your app. Don’t call it to clean up after yourself.

workser.agents

Use Agent Cloud to run an agent inside your product:
These calls belong on your server. Authenticate the app user and authorize access to the agent and run before returning progress or results. run() accepts work asynchronously; inspect the final status rather than treating acceptance as completion. See Agent Cloud for the full flow and usage boundary. For product agents, pass referenceUserId so the session and audit record identify the app user the agent is serving. Use runAndWait() for a backend job that can hold a connection, listRuns() to query history and cancelRun() to stop active work. The event stream resumes from its last handled sequence when a retryable connection drops.

Workflows

An app can run a workflow and nothing else — listing or inspecting workflows requires a user session, so it 401s from an app by design. Browse them in Workser Code or with workser workflow list.
A workflow feature is two-way. Triggering is the outbound half; when the workflow produces something the app needs, its final node has to POST back to a webhook route in your app:
Build only the trigger and the workflow runs perfectly while nothing ever appears in the product.

Security posture

This SDK is used by AI agents, so anything it returns can end up in a model’s context window, a transcript, or a log. It’s built accordingly — and every one of these is covered by a test in the package.
A Workser key grants access to a whole project, and bundlers inline process.env. Constructing a client with a secret key in a browser or React Native throws BrowserSecretError.The fix is never an allowBrowser flag — it’s to call Workser from your own server and let the client talk to that.
The base URL must be https, or loopback for local development. Anything else is refused with code: 'config' rather than putting a key on the wire.
Keys live only in the Authorization header. Errors are redacted, and fields named like secrets are dropped entirely rather than masked.
db.query(sql, params) binds server-side; there is no string-concatenation API to reach for, and building one on top defeats the point.
The SDK retries automatically, so a create without a key can become two orders. Pass { idempotencyKey }.

Errors

Every failure is a WorkserError with a stable code. Branch on the code, never on message text — messages change, codes don’t.
Also on the error: status, request (method, path, status, requestId), details, retryable.
err.summary() is the one to log. Logging the raw error object, or a config containing a key, is how a credential reaches a log aggregator, a Sentry event, or an agent’s context — and then has to be rotated.retryable failures have already been retried (2 by default) before you see them. Don’t wrap the call in your own retry loop on top.

Escape hatches

Workser’s API moves faster than this package is republished, and being blocked on an npm release to reach an endpoint you already own would be a bad trade:

Environment variables

All injected automatically in a Workser app: Other client options: timeoutMs (default 30s), maxRetries (default 2), headers (never put a credential here).