Node.js SDK
The SDK is a thin client for the Asqary API. It handles authentication, identifier hashing, retries, timeouts, request IDs and typed errors. It needs Node.js 20.3 or later, ships TypeScript types, supports ESM and CommonJS, and has no runtime dependencies.
Install
Section titled “Install”npm install @twalle/asqaryConfigure
Section titled “Configure”You need two secrets, both kept on your server:
| Setting | Environment variable | What it is |
|---|---|---|
apiKey |
ASQARY_API_KEY |
Your API key. asq_test_… keys use test data and asq_live_… keys use live data. |
identifierKey |
ASQARY_IDENTIFIER_KEY |
Your key for hashing identifiers. You generate it and keep it. |
import { Asqary } from "@twalle/asqary";
const asqary = new Asqary({ apiKey: process.env.ASQARY_API_KEY, identifierKey: process.env.ASQARY_IDENTIFIER_KEY, baseUrl: "https://api-staging.asqary.com", // the default until production is live timeout: 30_000, // ms per attempt maxRetries: 2,});Create one client and share it. It is safe for concurrent use.
Score a transaction
Section titled “Score a transaction”const decision = await asqary.score({ transactionId: "txn_123", type: "loan_disbursement", amount: 150_000, // minor units: ₦1,500.00 currency: "NGN", occurredAt: new Date(), customerId: "customer_456", deviceId: "device_789", beneficiaryAccount: "0123456789", beneficiaryBankCode: "058",});
switch (decision.recommendedAction) { case "approve": return approve(); case "step_up": return requestAdditionalVerification(); case "manual_review": return sendToReviewQueue(decision.decisionId); case "block": return decline();}Give each identifier either raw (customerId, hashed for you) or already hashed (customerIdHash), never both.
Each reason has a code, a contribution in points, a message and details with the evidence:
for (const reason of decision.reasons) { console.log(`${reason.code} +${reason.contribution}: ${reason.message}`);}Handle errors
Section titled “Handle errors”Every error is an AsqaryError with code, message, statusCode, requestId (quote it to support), details and retryable.
import { AsqaryError, RateLimitError, ValidationError } from "@twalle/asqary";
try { await asqary.score(transaction);} catch (error) { if (error instanceof ValidationError) { console.error(error.fieldErrors); } else if (error instanceof RateLimitError) { console.error(`retry in ${error.retryAfter}s`); } else if (error instanceof AsqaryError) { console.error(error.code, error.statusCode, error.requestId); } throw error;}Errors never include your API key, identifier key or transaction data. See Errors for the full list.
Retries, timeouts and idempotency
Section titled “Retries, timeouts and idempotency”- Retries: up to
maxRetries(default 2), with exponential backoff and jitter. 429, 502 and 503 are always retried. Network errors, timeouts, 500 and 504 are retried when the call is safe to repeat. 400, 401, 403, 404, 409, 413 and 422 are never retried. Retries reuse the same request ID and idempotency key. - Timeouts: 30 seconds per attempt by default. Override per client or per call:
asqary.score(tx, { timeout: 2_000 }). - Idempotency: scoring is idempotent on
transactionId. Creating policies, backtests and feedback sends anIdempotency-Key, which the SDK generates per call. Pass your own to make retries safe across restarts:asqary.policies.create(input, { idempotencyKey: "policy-123" }). - Cancellation: pass an
AbortSignal:asqary.score(tx, { signal }). - Request IDs: every call has one, on
decision.requestIdanderror.requestId. Pass your own with{ requestId }to correlate with your logs.
Other resources
Section titled “Other resources”await asqary.rules.list();await asqary.policies.create({ /* … */ });await asqary.policies.activate(policy.policyId);await asqary.decisions.get(decision.decisionId);await asqary.backtests.create({ /* … */ });await asqary.webhooks.create({ /* … */ });await asqary.feedback.create({ /* … */ });These are covered in Policies, Backtesting, Webhooks and Feedback.
Privacy
Section titled “Privacy”API keys, identifier keys and payloads are never logged or included in errors. Raw identifiers are hashed in your process and only hashes are sent. HTTPS is required (plain HTTP is allowed only for localhost). The SDK sends no telemetry and stores nothing on disk.