{"id":21043410,"url":"https://github.com/crystallizeapi/js-api-client","last_synced_at":"2026-04-23T22:00:51.488Z","repository":{"id":56304110,"uuid":"472517760","full_name":"CrystallizeAPI/js-api-client","owner":"CrystallizeAPI","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-23T18:02:39.000Z","size":372,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T20:05:18.614Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://crystallizeapi.github.io/libraries/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CrystallizeAPI.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-21T21:25:48.000Z","updated_at":"2026-04-23T18:02:00.000Z","dependencies_parsed_at":"2024-01-30T23:33:20.474Z","dependency_job_id":"a2b1fc9d-ebb3-4ecc-b0ad-7ecf388c176c","html_url":"https://github.com/CrystallizeAPI/js-api-client","commit_stats":{"total_commits":101,"total_committers":8,"mean_commits":12.625,"dds":0.2376237623762376,"last_synced_commit":"a297426a711fecbe12f0e02efa673c65a84c23b8"},"previous_names":[],"tags_count":68,"template":false,"template_full_name":null,"purl":"pkg:github/CrystallizeAPI/js-api-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fjs-api-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fjs-api-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fjs-api-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fjs-api-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CrystallizeAPI","download_url":"https://codeload.github.com/CrystallizeAPI/js-api-client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fjs-api-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32200159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T20:19:26.138Z","status":"ssl_error","status_checked_at":"2026-04-23T20:19:23.520Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T14:12:42.279Z","updated_at":"2026-04-23T22:00:51.480Z","avatar_url":"https://github.com/CrystallizeAPI.png","language":"TypeScript","readme":"# JS API Client\n\nHelpers and typed utilities for working with the Crystallize APIs.\n\n\u003e v5 is a major revamp: simpler client, typed inputs via @crystallize/schema, and focused managers for common tasks (catalogue, navigation, hydration, orders, customers, subscriptions, and cart).\n\n## Installation\n\n```bash\npnpm add @crystallize/js-api-client\n# or\nnpm install @crystallize/js-api-client\n# or\nyarn add @crystallize/js-api-client\n```\n\n## Quick start\n\n```typescript\nimport { createClient } from '@crystallize/js-api-client';\n\nconst api = createClient({\n    tenantIdentifier: 'furniture',\n    // For protected APIs, provide credentials\n    // accessTokenId: '…',\n    // accessTokenSecret: '…',\n    // staticAuthToken: '…',\n    // and more\n});\n\n// Call any GraphQL you already have (string query + variables)\nconst { catalogue } = await api.catalogueApi(\n    `query Q($path: String!, $language: String!) {\n    catalogue(path: $path, language: $language) { name path }\n}`,\n    { path: '/shop', language: 'en' },\n);\n\n// Don't forget to close when using HTTP/2 option (see below)\napi.close();\n```\n\n## Quick summary\n\n- One client with callers: `catalogueApi`, `discoveryApi`, `pimApi`, `nextPimApi`, `shopCartApi`\n- High-level helpers: `createCatalogueFetcher`, `createNavigationFetcher`, `createProductHydrater`, `createOrderFetcher`, `createOrderManager`, `createCustomerManager`, `createCustomerGroupManager`, `createSubscriptionContractManager`, `createCartManager`\n- Utilities: `createSignatureVerifier`, `createPluginPayloadDecrypter`, `createBinaryFileManager`, `pricesForUsageOnTier`, request `profiling`\n- Build GraphQL with objects using `json-to-graphql-query` (see section below)\n- Strong typing via `@crystallize/schema` inputs and outputs\n- Upgrading? See [UPGRADE.md](UPGRADE.md) for v4 → v5 migration\n\n## Options and environment\n\n`createClient(configuration, options?)`\n\n- configuration\n    - `tenantIdentifier` (required)\n    - `tenantId` optional\n    - `accessTokenId` / `accessTokenSecret` or `sessionId`\n    - `staticAuthToken` for read-only catalogue/discovery\n    - `shopApiToken` optional; otherwise auto-fetched\n    - `shopApiStaging` to use the staging Shop API\n    - `origin` custom host suffix (defaults to `.crystallize.com`)\n- options\n    - `useHttp2` enable HTTP/2 transport\n    - `timeout` request timeout in milliseconds; requests that take longer will be aborted\n    - `http2IdleTimeout` HTTP/2 idle timeout in milliseconds (default `300000` — 5 minutes). Use a shorter value for serverless functions, a longer one for long-running servers\n    - `profiling` callbacks\n    - `extraHeaders` extra request headers for all calls\n    - `shopApiToken` controls auto-fetch: `{ doNotFetch?: boolean; scopes?: string[]; expiresIn?: number }`\n\n`client.close()` should be called when you enable HTTP/2 to gracefully close the underlying session.\n\n### Available API callers\n\n- `catalogueApi` – Catalogue GraphQL\n- `discoveryApi` – Discovery GraphQL (replaces the old Search API)\n- `pimApi` – PIM GraphQL (classic /graphql soon legacy)\n- `nextPimApi` – PIM Next GraphQL (scoped to tenant)\n- `shopCartApi` – Shop Cart GraphQL (token handled for you)\n\nAll callers share the same signature: `\u003cT\u003e(query: string, variables?: Record\u003cstring, unknown\u003e) =\u003e Promise\u003cT\u003e`.\n\n### Authentication overview\n\nPass the relevant credentials to `createClient`:\n\n- `staticAuthToken` for catalogue/discovery read-only\n- `accessTokenId` + `accessTokenSecret` (or `sessionId`) for PIM/Shop operations\n- `shopApiToken` optional; if omitted, a token will be fetched using your PIM credentials on first cart call\n\nSee the official docs for auth: https://crystallize.com/learn/developer-guides/api-overview/authentication\n\n### Error handling\n\nAPI call errors throw a `JSApiClientCallError` with both `code` and `statusCode` properties for the HTTP status:\n\n```typescript\nimport { JSApiClientCallError } from '@crystallize/js-api-client';\n\ntry {\n    await api.pimApi(`query { … }`);\n} catch (e) {\n    if (e instanceof JSApiClientCallError) {\n        console.error(`HTTP ${e.statusCode}:`, e.message);\n        // e.code also works (same value)\n    }\n}\n```\n\n## Profiling requests\n\nLog queries, timings and server timing if available.\n\n```typescript\nimport { createClient } from '@crystallize/js-api-client';\n\nconst api = createClient(\n    { tenantIdentifier: 'furniture' },\n    {\n        profiling: {\n            onRequest: (q) =\u003e console.debug('[CRYSTALLIZE] \u003e', q),\n            onRequestResolved: ({ resolutionTimeMs, serverTimeMs }, q) =\u003e\n                console.debug('[CRYSTALLIZE] \u003c', resolutionTimeMs, 'ms (server', serverTimeMs, 'ms)'),\n        },\n    },\n);\n```\n\n## GraphQL builder: json-to-graphql-query\n\nThis library embraces the awesome [json-to-graphql-query](https://www.npmjs.com/package/json-to-graphql-query) under the hood so you can build GraphQL queries using plain JS objects. Most helpers accept an object and transform it into a GraphQL string for you.\n\n- You can still call the low-level callers with raw strings.\n- For catalogue-related helpers, we expose `catalogueFetcherGraphqlBuilder` to compose reusable fragments.\n\nExample object → query string:\n\n```typescript\nimport { jsonToGraphQLQuery } from 'json-to-graphql-query';\n\nconst query = jsonToGraphQLQuery({\n    query: {\n        catalogue: {\n            __args: { path: '/shop', language: 'en' },\n            name: true,\n            path: true,\n        },\n    },\n});\n```\n\n## High-level helpers\n\nThese helpers build queries, validate inputs using `@crystallize/schema`, and call the correct API for you.\n\n### Catalogue Fetcher\n\n```typescript\nimport { createCatalogueFetcher, catalogueFetcherGraphqlBuilder as b } from '@crystallize/js-api-client';\n\nconst fetchCatalogue = createCatalogueFetcher(api);\n\nconst data = await fetchCatalogue\u003c{ catalogue: { name: string; path: string } }\u003e({\n    catalogue: {\n        __args: { path: '/shop', language: 'en' },\n        name: true,\n        path: true,\n        ...b.onProduct({}, { onVariant: { sku: true, name: true } }),\n    },\n});\n```\n\n### Navigation Fetcher\n\n```typescript\nimport { createNavigationFetcher } from '@crystallize/js-api-client';\n\nconst nav = createNavigationFetcher(api);\nconst tree = await nav.byFolders('/', 'en', 3, /* extra root-level query */ undefined, (level) =\u003e {\n    if (level === 1) return { shape: { identifier: true } };\n    return {};\n});\n```\n\n### Product Hydrater\n\nFetch product/variant data by paths or SKUs with optional price contexts.\n\n```typescript\nimport { createProductHydrater } from '@crystallize/js-api-client';\n\nconst hydrater = createProductHydrater(api, {\n    marketIdentifiers: ['eu'],\n    priceList: 'b2b',\n    priceForEveryone: true,\n});\n\nconst products = await hydrater.bySkus(\n    ['SKU-1', 'SKU-2'],\n    'en',\n    /* extraQuery */ undefined,\n    (sku) =\u003e ({ vatType: { name: true, percent: true } }),\n    () =\u003e ({ priceVariants: { identifier: true, price: true } }),\n);\n```\n\n### Order Fetcher\n\n```typescript\nimport { createOrderFetcher } from '@crystallize/js-api-client';\n\nconst orders = createOrderFetcher(api);\nconst order = await orders.byId('order-id', {\n    onOrder: { payment: { provider: true } },\n    onOrderItem: { subscription: { status: true } },\n    onCustomer: { email: true },\n});\n\nconst list = await orders.byCustomerIdentifier('customer-123', { first: 20 });\n```\n\nTyped example (TypeScript generics):\n\n```typescript\ntype OrderExtras = { payment: { provider: string }[] };\ntype OrderItemExtras = { subscription?: { status?: string } };\ntype CustomerExtras = { email?: string };\n\nconst typedOrder = await orders.byId\u003cOrderExtras, OrderItemExtras, CustomerExtras\u003e('order-id', {\n    onOrder: { payment: { provider: true } },\n    onOrderItem: { subscription: { status: true } },\n    onCustomer: { email: true },\n});\n\ntypedOrder.payment; // typed as array with provider\ntypedOrder.cart[0].subscription?.status; // typed\ntypedOrder.customer.email; // typed\n```\n\n### Order Manager\n\nCreate/update orders, set payments or move to pipeline stage. Inputs are validated against `@crystallize/schema`.\n\n```typescript\nimport { createOrderManager } from '@crystallize/js-api-client';\n\nconst om = createOrderManager(api);\n\n// Register (minimal example)\nconst confirmation = await om.register({\n    cart: [{ sku: 'SKU-1', name: 'Product', quantity: 1, price: { gross: 100, net: 80, currency: 'USD' } }],\n    customer: { identifier: 'customer-123' },\n});\n\n// Update payments only\nawait om.setPayments('order-id', [\n    {\n        provider: 'STRIPE',\n        amount: { gross: 100, net: 80, currency: 'USD' },\n        method: 'card',\n    },\n]);\n\n// Put in pipeline stage\nawait om.putInPipelineStage({ id: 'order-id', pipelineId: 'pipeline', stageId: 'stage' });\n```\n\n### Customer and Customer Group Managers\n\n```typescript\nimport { createCustomerManager, createCustomerGroupManager } from '@crystallize/js-api-client';\n\nconst customers = createCustomerManager(api);\nawait customers.create({ identifier: 'cust-1', email: 'john@doe.com' });\nawait customers.update({ identifier: 'cust-1', firstName: 'John' });\n\nconst groups = createCustomerGroupManager(api);\nawait groups.create({ identifier: 'vip', name: 'VIP' });\n```\n\n### Subscription Contract Manager\n\nCreate/update contracts and generate a pre-filled template from a variant.\n\n```typescript\nimport { createSubscriptionContractManager } from '@crystallize/js-api-client';\n\nconst scm = createSubscriptionContractManager(api);\n\nconst template = await scm.createTemplateBasedOnVariantIdentity(\n    '/shop/my-product',\n    'SKU-1',\n    'plan-identifier',\n    'period-id',\n    'default',\n    'en',\n);\n\n// …tweak template and create\nconst created = await scm.create({\n    customerIdentifier: 'customer-123',\n    tenantId: 'tenant-id',\n    payment: {\n        /* … */\n    },\n    ...template,\n});\n```\n\n### Cart Manager (Shop API)\n\nToken handling is automatic (unless you pass `shopApiToken` and set `shopApiToken.doNotFetch: true`).\n\n```typescript\nimport { createCartManager } from '@crystallize/js-api-client';\n\nconst cart = createCartManager(api);\n\n// Hydrate a cart from input\nconst hydrated = await cart.hydrate({\n    language: 'en',\n    items: [{ sku: 'SKU-1', quantity: 1 }],\n});\n\n// Add/remove items, abandon or place and fulfill the cart and assign the orderId\nawait cart.addSkuItem(hydrated.id, { sku: 'SKU-2', quantity: 2 });\nawait cart.setCustomer(hydrated.id, { identifier: 'customer-123', email: 'john@doe.com' });\nawait cart.setMeta(hydrated.id, { merge: true, meta: [{ key: 'source', value: 'web' }] });\nawait cart.abandon(hydrated.id);\nawait cart.place(hydrated.id);\nawait cart.fulfill(hydrated.id, orderId);\n```\n\n## Signature verification\n\nUse `createSignatureVerifier` to validate Crystallize signatures for webhooks, apps or frontend calls. The verifier decodes the HS256 JWT envelope with the shared secret and matches its `hmac` claim against a SHA-256 of the reconstructed challenge — all through the bundled `jose` and the platform's `crypto.subtle`, so you don't need to pass your own JWT or hashing implementation.\n\n```typescript\nimport { createSignatureVerifier } from '@crystallize/js-api-client';\n\nconst verify = createSignatureVerifier({ secret: process.env.CRYSTALLIZE_SIGNATURE_SECRET! });\n\n// POST example\nawait verify(signatureJwtFromHeader, {\n    url: request.url,\n    method: 'POST',\n    body: rawBodyString, // IMPORTANT: raw body\n});\n\n// GET webhook example (must pass the original webhook URL)\nawait verify(signatureJwtFromHeader, {\n    url: request.url, // the received URL including query params\n    method: 'GET',\n    webhookUrl: 'https://example.com/api/webhook', // the configured webhook URL in Crystallize\n});\n```\n\n## Plugin payload decryption\n\nUse `createPluginPayloadDecrypter` to decrypt a Crystallize plugin JWE payload (outer JWE → nested JWS envelope → per-field `encryptedSecrets`) and optionally verify the inner JWS against a JWKS. This is the single entry point the CLI and any vendor-side integration should rely on.\n\nThe factory takes the vendor's private JWK (as produced by `crystallize plugin keygen`) and optional `verify` settings, and returns a reusable function that accepts a JWE compact payload per call. The private key and the JWKS resolver are built once and reused — so for a server handling many webhook calls, create the decrypter once at boot.\n\nOnly `RSA-OAEP` / `RSA-OAEP-256` with `A*GCM` content encryption are accepted on the outer JWE. When the outer header carries `cty: \"JWT\"`, the plaintext is treated as a compact JWS whose claims form the envelope.\n\nSignature verification is opt-in: pass `verify` to enable it. When `verify` is omitted — or when verification fails — the envelope and per-field secrets are still returned so the caller can inspect them; `signature.verified` / `signature.skipped` / `signature.reason` tell you whether to trust the result.\n\n```typescript\nimport { readFile } from 'node:fs/promises';\nimport { createPluginPayloadDecrypter } from '@crystallize/js-api-client';\n\nconst privateJwk = JSON.parse(await readFile('./private.jwk.json', 'utf8'));\n\n// Decrypt only — no signature check. Good for local dev / smoke tests.\nconst decrypt = createPluginPayloadDecrypter({ privateJwk });\nconst decoded = await decrypt(jweCompact);\n\nif (decoded.envelope) {\n    console.log('tenant:', decoded.envelope.tenantIdentifier);\n    console.log('config:', decoded.envelope.config);\n    console.log('secrets:', decoded.secrets); // { StripeApiKey: 'sk_live_…', … }\n}\n```\n\nEnable verification by passing `verify` with at least an `audience` (your plugin identifier). `issuer` defaults to `https://api.crystallize.com` and `jwksUrl` defaults to `${issuer}/.well-known/jwks.json`, so production usage is a one-liner:\n\n```typescript\n// Production — issuer + JWKS URL default to api.crystallize.com.\nconst decrypt = createPluginPayloadDecrypter({\n    privateJwk,\n    verify: { audience: 'com.vendor.plugin' },\n});\n\nconst verified = await decrypt(jweCompact);\nif (!verified.signature.verified) {\n    // Signature check skipped or failed — envelope + secrets are still populated but MUST be treated as untrusted.\n    console.warn('signature not trusted:', verified.signature.reason);\n}\n```\n\nOther `verify` fields: `clockTolerance` (seconds, defaults to `30`), `verifyBackendToken` (also verify `envelope.backendToken` against the same JWKS, defaults to `false`).\n\nThe returned `DecryptedPluginPayload` contains:\n\n- `protectedHeader` — outer JWE protected header\n- `innerProtectedHeader` — inner JWS protected header, when the payload is nested\n- `envelope` — verified (or decoded) JWS claims, or `null` for a non-nested payload\n- `plaintext` — raw outer plaintext when the payload is not a nested JWT, otherwise `null`\n- `secrets` — plain-text per-field secrets decrypted from `envelope.encryptedSecrets`\n- `signature` — `{ verified, skipped?, reason?, issuer?, audience?, algorithm? }`\n- `backendToken` — `{ verified, skipped?, reason?, claims? }` when `envelope.backendToken` is present, otherwise `null`\n\n\u003e Security: `secrets` and decoded `envelope` claims contain cleartext credentials. Do not log or forward them to shared sinks.\n\n## Pricing utilities\n\n```typescript\nimport { pricesForUsageOnTier } from '@crystallize/js-api-client';\n\nconst usage = 1200;\nconst total = pricesForUsageOnTier(\n    usage,\n    [\n        { threshold: 0, price: 0, currency: 'USD' },\n        { threshold: 1000, price: 0.02, currency: 'USD' },\n    ],\n    'graduated',\n);\n```\n\n## Binary file manager\n\nUpload files (like images) to your tenant via pre-signed requests. Server-side only.\n\n```typescript\nimport { createBinaryFileManager } from '@crystallize/js-api-client';\n\nconst files = createBinaryFileManager(api);\nconst mediaKey = await files.uploadImage('/absolute/path/to/picture.jpg');\nconst staticKey = await files.uploadFile('/absolute/path/to/static/file.pdf');\nconst bulkKey = await files.uploadMassOperationFile('/absolute/path/to/import.zip');\n// Use the returned keys in subsequent PIM mutations\n```\n\n`uploadImage` validates that the file is an image before creating a `MEDIA` upload. Use `uploadFile` for assets that should live in the tenant's static file storage, and `uploadMassOperationFile` for imports handled by the mass operations pipeline. Call `uploadToTenant` directly if you need lower-level control (e.g., custom buffers or upload types).\n\n[crystallizeobject]: crystallize_marketing|folder|625619f6615e162541535959\n\n## Mass Call Client (Deprecated)\n\n\u003e **Deprecated:** Use mature ecosystem packages like [`p-limit`](https://www.npmjs.com/package/p-limit) or [`p-queue`](https://www.npmjs.com/package/p-queue) instead. They provide better error handling, TypeScript support, and are actively maintained.\n\n### Recommended alternative using p-limit\n\n```typescript\nimport pLimit from 'p-limit';\nimport { createClient } from '@crystallize/js-api-client';\n\nconst api = createClient({ tenantIdentifier: 'my-tenant', accessTokenId: '…', accessTokenSecret: '…' });\nconst limit = pLimit(5); // max 5 concurrent requests\n\nconst mutations = items.map((item) =\u003e\n    limit(() =\u003e\n        api.pimApi(\n            `mutation UpdateItem($id: ID!, $name: String!) { product { update(id: $id, input: { name: $name }) { id } } }`,\n            { id: item.id, name: item.name },\n        ),\n    ),\n);\n\nconst results = await Promise.allSettled(mutations);\nconst failed = results.filter((r) =\u003e r.status === 'rejected');\nconsole.log(`Done: ${results.length - failed.length} succeeded, ${failed.length} failed`);\n```\n\n### Legacy usage\n\nThe mass call client is still functional but will emit a deprecation warning on first use.\n\nSometimes, when you have many calls to do, whether they are queries or mutations, you want to be able to manage them asynchronously. This is the purpose of the Mass Call Client. It will let you be asynchronous, managing the heavy lifting of lifecycle, retry, incremental increase or decrease of the pace, etc.\n\nThese are the main features:\n\n- Run *initialSpawn* requests asynchronously in a batch. *initialSpawn* is the size of the batch by default\n- If there are more than 50% errors in the batch, it saves the errors and continues with a batch size of 1\n- If there are less than 50% errors in the batch, it saves the errors and continues with the current batch size minus 1\n- If there are no errors, it increments (+1) the number of requests in a batch, capped to *maxSpawn*\n- If the error rate is 100%, it waits based on **Fibonacci** increment\n- At the end of all batches, you can retry the failed requests\n- Optional lifecycle function *onBatchDone* (async)\n- Optional lifecycle function *onFailure* (sync) allowing you to do something and decide to let enqueue (return true: default) or return false and re-execute right away, or any other actions\n- Optional lifecycle function *beforeRequest* (sync) to execute before each request. You can return an altered request/promise\n- Optional lifecycle function *afterRequest* (sync) to execute after each request. You also get the result in there, if needed\n\n```javascript\nconst client = createMassCallClient(api, { initialSpawn: 1 });\n\nasync function run() {\n    for (let i = 1; i \u003c= 54; i++) {\n        client.enqueue.catalogueApi(`query { catalogue { id, key${i}: name } }`);\n    }\n\n    const successes = await client.execute();\n    console.log('First pass done ', successes);\n    console.log('Failed Count: ' + client.failureCount());\n    while (client.hasFailed()) {\n        console.log('Retrying...');\n        const newSuccesses = await client.retry();\n        console.log('Retry pass done ', newSuccesses);\n    }\n    console.log('ALL DONE!');\n}\nrun();\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystallizeapi%2Fjs-api-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrystallizeapi%2Fjs-api-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystallizeapi%2Fjs-api-client/lists"}