TypeScript SDK
@salesos/sdk — typed resources, retries, idempotency, pagination, OAuth helpers and webhook verification for Node 18+ and browsers.
@salesos/sdk is the official TypeScript client. Its types are generated from
the OpenAPI document and the
event catalog, and it implements the contracts in these
guides for you: retries and rate limits,
idempotency keys, pagination,
errors, OAuth and
webhook signatures.
It has no runtime dependencies and uses the platform's fetch and Web Crypto,
so the same package runs in Node 18+, Deno, Bun, workers and browsers. It ships
ES modules, CommonJS and type declarations.
The SDK is not on npm yet. Until it is, build it from the SalesOS repository
(packages/sdk-js) and install it from a path or tarball (npm pack).
Quick start
| Option | Default | |
|---|---|---|
apiKey | Sent as X-API-Key | |
accessToken | OAuth access token, or a function returning the current one | |
auth | Any credential provider (see Authentication) | |
baseUrl | https://www.salesos.org | Paths get /v1 added when they don't carry it |
timeoutMs | 30000 | Per attempt |
maxRetries | 2 | Extra attempts after the first |
maxRetryDelayMs | 30000 | A longer Retry-After is not waited for |
autoIdempotency | true | Adds an Idempotency-Key to every POST/PATCH |
organizationId | Sent as X-Organization-ID | |
fetch | globalThis.fetch | Bring your own (tests, proxies) |
onRetry | Called before each retry, for logging |
Resources
Each core object has list, iterate, pages, get, create, update and
delete, with request and response types from the OpenAPI document:
leads, contacts, accounts, opportunities, tasks, quotes, orders,
cases, and activities (no update; activities are immutable).
| Resource | Methods |
|---|---|
customObjects | list, get, create, update, delete, records(objectId) → list / iterate / create / update / delete |
webhooks | list, get, create, update, delete, activate, deactivate, rotateSecret, test(id, type), deliveries, delivery, replay |
events | catalog(), get(type) (no credentials needed) |
bulk | createJob, getJob, listJobs, cancelJob, waitForJob, results, failures |
customActions | list, get, create, update, delete, rotateSecret, test(id, input), run, invocations |
oauthApps | list, get, create, update, rotateSecret, delete (session only) |
developerOrgs | list, current, create, open, leave, delete, components, createChangeSet (session only) |
metadata | types, retrieve, deploy, runTests, listDeploys, getDeploy, checkAgain, cancel, waitForDeploy, getTestPolicy, setTestPolicy |
functions | list, get, create, update, delete, versions, createVersion, deploy, rollback, setSecret, deleteSecret, test, invoke, invocations, invocation |
eventStream | listen, trigger (see listen) |
apiUsage | summary |
Everything else is one call away:
Or by operationId, fully typed from the spec. The operation table lives in a
subpath so it stays out of bundles that don't use it:
Pagination
list() returns one page in the list envelope
(data, page, limit, total, totalPages, hasMore). iterate() walks
every page lazily, 100 records per request unless you pass limit (the server
caps it at 200):
Errors
Every failure is a SalesOSError:
| Field | |
|---|---|
status | HTTP status, or 0 when no response arrived |
code | The API's stable code (for example VALIDATION_RULE_FAILED), the OAuth error, HTTP_<status>, or NETWORK_ERROR / TIMEOUT / ABORTED |
message | Validation messages are joined with ; |
requestId | Quote it to support. The SDK sends an X-Request-ID on every request, so there is always one |
retryAfterMs, body, headers, method, url |
Retries and idempotency
GET,HEADandOPTIONSare retried on network errors, timeouts,408,500,502,503and504, with exponential backoff and jitter.- A
Retry-Afterheader is honoured (seconds or an HTTP date). If it is longer thanmaxRetryDelayMsthe error is thrown instead, withretryAfterMsset. 429withRetry-Afteris retried for any method: the request was refused before it ran.- Writes are retried only when they carry an
Idempotency-Key. WithautoIdempotency(the default) everyPOSTandPATCHgets a random key, reused across its retries, so a retry replays the first response instead of creating a duplicate. A409 IDEMPOTENCY_REQUEST_IN_PROGRESSwaits and tries again. PUTandDELETEare not retried unless you passidempotent: true. Multipart uploads are never retried, because the server doesn't deduplicate them.
Authentication
Client credentials
The token is fetched on first use and again a minute before it expires;
concurrent requests share one fetch, and a 401 triggers one fresh token.
Authorization code + PKCE
OAuthTokenAuth refreshes a minute before expiry and once after a 401, and
runs one refresh at a time. That matters: presenting a refresh token that was
already used revokes the whole token family, so two parallel refreshes would
sign the user out. The token endpoint itself is never retried for the same
reason.
oauth.refresh(), oauth.revoke(token, hint), oauth.introspect(token) and
oauth.userinfo(accessToken) are available directly too.
Sessions
OAuth app management and developer orgs refuse API keys and OAuth tokens. For
those, SessionAuth sends a signed-in user's token and adds the CSRF token to
writes. developerOrgs.open() and leave() return a new session, and the
client switches to it automatically.
Webhooks
Verify against the raw body, before any JSON parsing:
headers can be a Headers object, a Node header record or the
X-SalesOS-Signature value itself. Timestamps more than toleranceSec (default
300) from your clock are rejected, and any matching v1= is accepted, so a
rotated secret keeps working during the overlap. Dedupe on event.id: it is
stable across retries and replays. signWebhookPayload() produces the same
header SalesOS sends, for your own tests.
Types: WebhookEvent<'lead.created'>, AnyWebhookEvent (a discriminated
union), WebhookEventDataMap, WEBHOOK_EVENT_TYPES, and LegacyWebhookEvent
for webhooks still on payloadVersion: 1.
Bulk jobs
Inline input works too: { object: 'lead', operation: 'insert', records: [...] }.
See Bulk data for formats and limits.
Types
Every component schema is available as Schemas['Name'], and every operation as
Operations['operationId'] (pathParams, query, body, response,
scopes). Shortcuts exist for the core objects: Lead, LeadCreate,
LeadUpdate, LeadListQuery, and the same for the other resources.
API_VERSION is the spec version the types were generated from; responses
carry the server's own in the API-Version header (see
versioning).
Metadata, functions and live events
These resources use hand-written types until the next type regeneration from the
live spec. METADATA_TYPES lists every component type the metadata API takes
(MetadataType is its union). See Metadata as code and
Testing.