Node.js SDK
Connect customer records, events, and prepared campaigns from your Node.js or TypeScript service. The client exposes the Public API through resource methods, typed errors, and pagination helpers.
Current API coverage
The SDK groups Public API calls by resource. Use the API reference for request fields, response payloads, and operation semantics, and the examples below for language-specific method calls.
Pagination helpers follow the API traversal limits. An iterator or method named all stops at the traversal cap; use scoped queries when working with larger datasets.
Install
Install the SDK with npm:
npm install maxclicks
Authenticate
Pass an API key to new Maxclicks(...), or set MAXCLICKS_API_KEY and call new Maxclicks().
import { Maxclicks } from 'maxclicks'
const mc = new Maxclicks('max_...')
const me = await mc.me()
console.log(me.space?.name)
CommonJS works too:
const { Maxclicks } = require('maxclicks')
Configuration
- Name
apiKey- Type
- string
- Description
Your API key. Defaults to the
MAXCLICKS_API_KEYenvironment variable.
- Name
baseUrl- Type
- string
- Description
API base URL. Defaults to
https://api.maxclicks.ai/v1.
- Name
timeoutMs- Type
- number
- Description
Per-request timeout. Defaults to
60000.
- Name
maxRetries- Type
- number
- Description
Extra attempts on
429, plus5xxand transport errors for reads and calls that carry an idempotency key. Defaults to2.
- Name
onWarning- Type
- function
- Description
Callback for non-fatal API warnings.
const mc = new Maxclicks({
apiKey: process.env.MAXCLICKS_API_KEY,
baseUrl: 'https://api.maxclicks.ai/v1',
timeoutMs: 60000,
maxRetries: 2,
onWarning: (warnings) => console.warn(warnings),
})
Send and upsert
schema is a schema id or slug. Records are flat: base fields plus your custom attribute values at the top level.
// Upsert a contact by identity (id, userId, email, or phone).
const contact = await mc.records.upsert('students', {
email: '[email protected]',
firstName: 'Ada',
tags: ['beta'],
})
// Send a template to a contact resolved by email.
await mc.templates.send(templateId, {
data: { contact: { email: '[email protected]', firstName: 'Ada' } },
})
// Fire an event.
await mc.events.fire('purchase-completed', { eventId: 'ord_123', amount: 42 })
records.create is a strict create and returns 409 if the identity exists. records.upsert creates or updates by identity. See Contacts for the identity cascade.
Follow asynchronous work
Acceptance is not completion. An event's receipt carries its id, which events.status reads for readiness. A record deletion returns an operation that you poll until it completes.
const receipt = await mc.events.fire('purchase-completed', {
eventId: 'ord_123',
amount: 42,
})
const { readinessState } = await mc.events.status(
'purchase-completed',
receipt.id,
)
const deletion = await mc.records.delete('students', contact.id)
const { status } = await mc.records.getDeletion(deletion.operationId)
readinessState is pending, ready, failed or outcome_unknown. A deletion is pending, blocked, completed or cancelled; while it reports canCancel, the user who requested it can stop it with records.cancelDeletion. templates.send and records.delete always send an Idempotency-Key, generating one when you pass none. To retry later without sending or deleting twice, pass your own { idempotencyKey } and reuse it. Older workflow run history pages with workflows.getRun(id, runId, { historyBefore }).
Pagination
Every offset-paginated list returns a Page. A Page is both a single page and an async iterable over every item across all pages. It auto-fetches subsequent pages as you iterate. events.list is the exception: it is cursor-paginated and returns a plain EventPage with data, pagination and warnings, which you page by passing pagination.nextCursor back as cursor.
// One page.
const page = await mc.records.list('students', { limit: 100 })
console.log(page.data, page.pagination.totalCount, page.pagination.hasMore)
// Every record, auto-fetching subsequent pages.
for await (const record of await mc.records.list('students')) {
console.log(record.id)
}
// Or collect them all.
const all = await (await mc.records.list('students')).all()
Errors
Every failure throws a typed subclass of MaxclicksError. The happy path returns data directly.
import {
MaxclicksError,
MaxclicksNotFoundError,
MaxclicksRateLimitError,
} from 'maxclicks'
try {
const record = await mc.records.get('students', 'missing-id')
} catch (error) {
if (error instanceof MaxclicksNotFoundError) {
// 404
} else if (error instanceof MaxclicksRateLimitError) {
console.log('retry after', error.retryAfterMs)
} else if (error instanceof MaxclicksError) {
console.error(error.status, error.code, error.type, error.message)
}
}
| Error | When |
|---|---|
MaxclicksBadRequestError | 400 validation failure |
MaxclicksAuthenticationError | 401 missing or invalid API key |
MaxclicksPermissionError | 403 insufficient permission or space out of scope |
MaxclicksNotFoundError | 404 resource not found |
MaxclicksConflictError | 409 identifier or uniqueness conflict |
MaxclicksUnprocessableEntityError | 422 (for example a batch abort) |
MaxclicksRateLimitError | 429 rate limited (carries retryAfterMs) |
MaxclicksServerError | 5xx server error |
MaxclicksConnectionError / MaxclicksTimeoutError | transport failure or timeout |
MaxclicksConfigurationError | client misconfiguration (for example no API key) |
The SDK retries reads on 429, 5xx, and transport errors with jittered
exponential backoff, honoring the Retry-After header. A write is only
retried on 5xx and transport errors when it carries an idempotency key, one
you pass or the one templates.send and records.delete generate, so your
code can inspect an uncertain outcome. Every request is still retried on
429. Tune the attempt count with maxRetries.
Retry outcomes
A repeated write can return a recorded result, including an error. If the API reports idempotency_outcome_unknown, inspect the affected resource or run history before creating a new operation. Keep the original key and payload while you reconcile. See the idempotency guide for supported operations and retention.