TypeScript SDK Reference

Authoritative reference for the official Claimful TypeScript SDK (@claimful/sdk, source at packages/sdk-ts). Pair this document with the SDK semantic-versioning covenant — breaking-change rules and the 90-day deprecation-overlap window live there.

Document marker: CLAIMFUL_SDK_TS_SENTINEL

1. Install

pnpm add @claimful/sdk

Requirements:

  • Node >=22 (matches engines.node in package.json).
  • A fetch implementation (Node 22 ships a global; you may inject your own).
  • A merchant secret API key for server-side calls (mch_live_... / mch_test_...). The browser widget uses a separate publishable wk_ key — never send a secret mch_ key from the browser.

Dual ESM + CJS with TypeScript declarations.

2. Client construction

import { Claimful } from '@claimful/sdk';

const client = new Claimful({
  apiKey: process.env.CLAIMFUL_API_KEY!,
  baseUrl: 'https://api.claimful.ai/api/v1', // optional, default shown
  fetch: globalThis.fetch,                    // optional override
  retry: {                                    // optional, defaults shown
    baseDelayMs: 250,
    jitter: true,
    maxAttempts: 3,
    multiplier: 2,
    retryAfterCeilingSeconds: 600,
  },
});

ClaimfulClient is an alias of Claimful. Construction throws ClaimfulValidationError for an empty apiKey or a missing fetch implementation.

Retries fire on HTTP 408, 429, 500, 502, 503, 504. Retry-After is honoured up to retryAfterCeilingSeconds.

Idempotency contract (Table 34 / ADR 0018):

  • Keys and their cached responses are valid for 24 hours. Within that window, resending the same key replays the original response with no side-effects.
  • Reusing the same key with a different request body returns 409 idempotency_conflict. Generate a new key for a distinct request.
  • A missing or invalid key returns 400 idempotency_key_required. The SDK auto-injects UUIDv4 keys so raw-HTTP callers are the primary audience for this error.

3. Public methods

client.offers

| Method | HTTP | | --- | --- | | offers.quote(input, options?) | POST /offers/quote (returns QuoteResponse) | | offers.confirm(input, options?) | POST /offers/confirm | | offers.decline(input, options?) | POST /offers/decline | | offers.void(input, options?) | POST /offers/void |

interface QuoteRequest {
  currency: 'USD' | string;
  customerEmail?: string;
  orderAmountCents: number;
  orderId: string;
  protectionProgramId: string;
}

interface QuoteResponse {
  expiresAt: string;
  fee: number;
  quoteId: string;
}

interface QuoteConfirmRequest {
  quoteId: string;
  quoteToken: string;
}

client.claims

| Method | HTTP | | --- | --- | | claims.list(params?) | GET /merchant/claims (optional limit, status) |

The bare GET /claims path is portal/consumer-side only; the merchant-key claim list lives under /merchant/claims (FER-189).

client.refunds

| Method | HTTP | | --- | --- | | refunds.create(orderId, payload?, options?) | POST /orders/{orderId}/refund |

The refund is keyed by orderId in the path; the payload bag is reserved for future API versions.

client.webhooks

Two aliases of the top-level verifyWebhook export — client.webhooks.verify and client.webhooks.verifyWebhook.

client.headless

Tier-1.5 helpers (ADR 0055 proposed). Body is canonicalised via canonicalStringify() so the wire bytes match the PHP server-side App\Headless\CanonicalJson::stringify AND the PHP SDK byte-for-byte.

| Method | HTTP | Response type | | --- | --- | --- | | headless.quote(input, options?) | POST /headless/quote | HeadlessQuoteResponse | | headless.confirm(input, options?) | POST /headless/confirm | HeadlessProtectedPurchaseResponse | | headless.decline(input, options?) | POST /headless/decline | HeadlessDeclineResponse |

The exported constant HEADLESS_DISCLOSURE_PHRASE holds the byte-string "This is not insurance". The server response disclosure_text always equals this constant. Use it only for byte-equality assertions — do not use it as visible disclosure copy. Any consumer-facing UI must render the full two-sentence phrase verbatim:

This is not insurance. This is a contractual refund guarantee provided by Claimful Inc.

verifyWebhook(rawBody, header, secret, toleranceSeconds?)

Top-level export. Returns a discriminated union:

type WebhookVerificationResult =
  | { verified: true; timestamp: number }
  | {
      verified: false;
      reason:
        | 'header_invalid'
        | 'signature_mismatch'
        | 'timestamp_invalid'
        | 'timestamp_outside_tolerance';
    };

HMAC-SHA-256 via crypto.subtle.sign, constant-time hex comparison.

canonicalStringify(value)

Lower-level helper for callers that want byte-identical canonical JSON without going through the headless helpers.

4. Error types

| Class | Extends | Thrown when | | --- | --- | --- | | ClaimfulValidationError | Error | Construction or arg validation. | | ClaimfulHttpError | Error | Non-2xx response (not 429). Fields: httpStatus, body, requestId, idempotencyKey. | | ClaimfulRateLimitError | ClaimfulHttpError | HTTP 429. Adds retryAfter. |

5. End-to-end example

import {
  Claimful,
  ClaimfulHttpError,
  ClaimfulRateLimitError,
  HEADLESS_DISCLOSURE_PHRASE,
  verifyWebhook,
} from '@claimful/sdk';

const claimful = new Claimful({ apiKey: process.env.CLAIMFUL_API_KEY! });

const offer = await claimful.offers.quote({
  currency: 'USD',
  orderAmountCents: 12_900,
  orderId: 'order_1234',
  protectionProgramId: 'pp_abc',
});

try {
  await claimful.offers.confirm(
    { quoteId: offer.quoteId, quoteToken: '<server-token>' },
    { idempotencyKey: 'example-order-1234' },
  );
} catch (err) {
  if (err instanceof ClaimfulRateLimitError) {
    await new Promise((r) => setTimeout(r, (err.retryAfter ?? 5) * 1000));
  } else if (err instanceof ClaimfulHttpError) {
    console.error('http', err.httpStatus, 'request', err.requestId);
    throw err;
  }
}

const headless = await claimful.headless.quote({
  orderAmountCents: 12_900,
  protectionProgramId: 'pp_abc',
});

if (headless.disclosure_text !== HEADLESS_DISCLOSURE_PHRASE) {
  throw new Error('disclosure drift');
}

const result = await verifyWebhook(
  rawBody,
  request.headers['x-claimful-signature']!,
  process.env.CLAIMFUL_WEBHOOK_SECRET!,
);

6. Confirmation email requirements [iii-3-8]

After a consumer opts in at checkout, your order confirmation email must include the following four items (contractual requirement — Merchant Agreement §3.2):

  1. Protection reference number — the CLM-YYYY-XXXXXXXX value returned in the confirm response (Crockford Base32 format, regex ^CLM-\d{4}-[0-9A-HJKMNP-TV-Z]{8}$).

  2. "Not insurance" disclosure — include this exact phrase verbatim:

    This is not insurance. This is a contractual refund guarantee provided by Claimful Inc.

  3. 48-hour free-look cancellation notice — inform the consumer they may request a full fee refund within 48 hours of purchase by visiting claimful.claims with their protection reference number.

  4. Covered-reasons summary or link — either list the 12 covered reasons or link to the consumer's coverage details page at claimful.claims.

Note: The claimful.claims coverage deep-link is delivered by Claimful directly via its own welcome email (welcome_protected). Your confirmation email does not need to reproduce the tokenized link — a plain claimful.claims reference is sufficient.

Sample template

Subject: Your order is confirmed + purchase protection is active

Hi [Customer Name],

Thank you for your order. You've added Claimful purchase protection
(reference: CLM-2026-7H2KQ9PM).

If you cannot attend for a covered reason, request a refund at claimful.claims
within 30 days of your event.

Free-look cancellation: You may cancel this protection for a full fee refund
within 48 hours of purchase at claimful.claims.

Disclosure: This is not insurance. This is a contractual refund guarantee
provided by Claimful Inc.

7. Cross-references