DutyClaims Docs
SDK Quickstarts

Start from the generated starters, then tighten types around your real workflow.

These quickstarts stay close to the generated DutyClaims artifacts. Use them when you want a copy-paste TypeScript starting point without pretending the thin starter files are a separate source of truth from the OpenAPI contract.

Generated starter bundle

Generated Mar 25, 2026, 7:30 PM from contract hash ae140b8f3b926d06.

StarterUse whenCredential postureDownload
Canonical partner client starterNew builds that should target canonical `/v1/*` routes first and keep auth handling in one thin fetch wrapper.`Authorization: Bearer ...` by defaultdutyclaims-partner-api.v1.client.ts
Generated TypeScript typesYou want the quickstart code to replace `unknown` with contract-backed request and response types.No runtime credential; pairs with the starter clientdutyclaims-partner-api.v1.types.d.ts
Broker compatibility client starterA staged broker migration still depends on `/broker/v1/*` aliases and you need the alias wrapper to stay explicit.Same managed credential posture as the canonical clientdutyclaims-broker-api.v1.client.ts
Webhook verification exampleYou need a working HMAC verification function before you trust any asynchronous lifecycle event.Uses the endpoint signing secret, not the API credentialverify-webhook.ts

Before you copy these snippets

  • Keep the starter files and the raw OpenAPI document on the same contract hash.
  • Prefer the canonical partner client for new `/v1/*` work.
  • Keep `Idempotency-Key` and `X-Correlation-Id` on your write paths from day one.
  • Use the generated types file to replace the quickstart `Record<string, unknown>` placeholders once your payloads stabilize.

Reference routes used in these quickstarts

Canonical partner flow quickstart

import { randomUUID } from "node:crypto"

import { DutyClaimsPartnerClient } from "./dutyclaims-partner-api.v1.client"

const client = new DutyClaimsPartnerClient({
  bearerToken: process.env["DUTYCLAIMS_TOKEN"],
})

async function main() {
  const correlationId = randomUUID()

  await client.registerWebhookEndpoint<Record<string, unknown>>({
    label: "partner-production",
    url: "https://partner.example.com/webhooks/dutyclaims",
    subscribedEvents: ["claim.updated", "partner.credential.rotated"],
    environmentScope: "production",
  })

  const partnerClient = await client.json<{ id: string }>("/v1/clients", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Correlation-Id": correlationId,
    },
    body: JSON.stringify({
      partnerClientId: "acme-importer-001",
      legalName: "Acme Imports, Inc.",
      displayName: "Acme Imports",
      importerOfRecordNumber: "12-3456789",
    }),
  })

  const claim = await client.json<{ id: string }>("/v1/claims", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Idempotency-Key": randomUUID(),
      "X-Correlation-Id": correlationId,
    },
    body: JSON.stringify({
      clientId: partnerClient.id,
      partnerClientId: "acme-importer-001",
      partnerClaimReference: "claim-2026-0001",
      importer: {
        legalName: "Acme Imports, Inc.",
        ein: "12-3456789",
        contactName: "Jordan Example",
        email: "jordan@example.com",
      },
      entries: [{}],
    }),
  })

  const status = await client.json<Record<string, unknown>>(
    `/v1/claims/${claim.id}/status`,
    {
      headers: {
        "X-Correlation-Id": correlationId,
      },
    }
  )

  console.log({ partnerClientId: partnerClient.id, claimId: claim.id, status })
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

Webhook verification quickstart

import { createHmac, timingSafeEqual } from "node:crypto"

const secret = process.env["DUTYCLAIMS_WEBHOOK_SECRET"] ?? "dcp_test_webhook_secret"

export function verifyDutyClaimsWebhook(input: {
  payload: string
  headers: Headers
}): boolean {
  const timestamp = input.headers.get("X-DutyClaims-Timestamp")
  const signature = input.headers.get("X-DutyClaims-Signature")

  if (!timestamp || !signature) return false

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${input.payload}`)
    .digest("hex")

  return (
    signature.length === expected.length &&
    timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  )
}

export function readDutyClaimsEvent(input: { headers: Headers }): string | null {
  return input.headers.get("X-DutyClaims-Event")
}

Diligence compatibility quickstart

import { basename } from "node:path"
import { readFile } from "node:fs/promises"

import { DutyClaimsPartnerClient } from "./dutyclaims-partner-api.v1.client"

async function main() {
  const csvPath = process.argv[2] ?? "./sample-entries.csv"
  const csvBytes = await readFile(csvPath)

  const formData = new FormData()
  formData.set("seller_name", "Acme Imports")
  formData.set("requires_protest_documentation", "false")
  // Replace this with your runtime's multipart helper if File is not global.
  formData.set("file", new File([csvBytes], basename(csvPath), { type: "text/csv" }))

  const client = new DutyClaimsPartnerClient({
    apiKey: process.env["DUTYCLAIMS_API_KEY"],
  })

  const accepted = await client.submitDiligence(formData)
  const { jobId } = (await accepted.json()) as { jobId: string }

  const status = await client.getDiligenceStatus<Record<string, unknown>>(jobId)

  console.log({ jobId, status })
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

This is the compatibility path that still publishes X-API-Key. New partner builds should still prefer canonical `/v1/*` flows where possible.

When to use the broker starter

  • Use it only when a broker migration still needs `/broker/v1/*` aliases in production traffic.
  • Keep the alias headers in your logs so you know which compatibility path is still live.
  • Move new integrations onto canonical `/v1/*` routes and treat the broker wrapper as a migration tool, not the permanent API shape.
  • Use the migration guide before expanding alias usage.

Generated canonical client starter

/**
 * Generated starter client for the DutyClaims Partner API.
 * This is intentionally thin: the canonical contract still lives in the OpenAPI document.
 */

export interface DutyClaimsPartnerClientOptions {
  baseUrl?: string
  bearerToken?: string
  apiKey?: string
  fetch?: typeof fetch
}

export interface DutyClaimsWebhookEndpointInput {
  label: string
  url: string
  subscribedEvents: string[]
  environmentScope?: "sandbox" | "production" | "all"
}

export class DutyClaimsPartnerClient {
  private readonly baseUrl: string
  private readonly fetchImpl: typeof fetch

  constructor(private readonly options: DutyClaimsPartnerClientOptions = {}) {
    this.baseUrl = options.baseUrl ?? "https://api.dutyclaims.com"
    this.fetchImpl = options.fetch ?? fetch
  }

  private buildHeaders(input?: HeadersInit): Headers {
    const headers = new Headers(input)

    if (this.options.bearerToken && !headers.has("Authorization")) {
      headers.set("Authorization", `Bearer ${this.options.bearerToken}`)
    }
    if (this.options.apiKey && !headers.has("X-API-Key")) {
      headers.set("X-API-Key", this.options.apiKey)
    }

    return headers
  }

  async request(path: string, init: RequestInit = {}): Promise<Response> {
    return this.fetchImpl(new URL(path, this.baseUrl), {
      ...init,
      headers: this.buildHeaders(init.headers),
    })
  }

  async json<T>(path: string, init: RequestInit = {}): Promise<T> {
    const response = await this.request(path, init)

    if (!response.ok) {
      const detail = await response.text()
      throw new Error(`DutyClaims request failed (${response.status}): ${detail}`)
    }

    return (await response.json()) as T
  }

  submitDiligence(formData: FormData): Promise<Response> {
    return this.request("/v1/diligence/submit", {
      method: "POST",
      body: formData,
    })
  }

  getDiligenceStatus<T = unknown>(jobId: string): Promise<T> {
    return this.json(`/v1/diligence/status/${encodeURIComponent(jobId)}`)
  }

  resumeDiligence<T = unknown>(jobId: string): Promise<T> {
    return this.json(`/v1/diligence/jobs/${encodeURIComponent(jobId)}/resume`, {
      method: "POST",
    })
  }

  registerWebhookEndpoint<T = unknown>(input: DutyClaimsWebhookEndpointInput): Promise<T> {
    return this.json("/v1/webhooks/endpoints", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(input),
    })
  }

  triggerSandboxWebhookTest<T = unknown>(input: {
    endpointId?: string
    eventType?: string
    environment?: "sandbox" | "production"
    payload?: Record<string, unknown>
  }): Promise<T> {
    return this.json("/v1/sandbox/webhooks/test", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(input),
    })
  }
}

Generated broker compatibility starter

import {
  DutyClaimsPartnerClient,
  type DutyClaimsPartnerClientOptions,
} from "./dutyclaims-partner-api.v1.client"

export type { DutyClaimsPartnerClientOptions }

export class DutyClaimsBrokerClient extends DutyClaimsPartnerClient {
  async onboard<T = unknown>(body: Record<string, unknown>): Promise<T> {
    return this.json("/broker/v1/onboard", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    })
  }

  async createClient<T = unknown>(body: Record<string, unknown>): Promise<T> {
    return this.json("/broker/v1/clients", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    })
  }

  async runPortfolioScan<T = unknown>(body: Record<string, unknown>): Promise<T> {
    return this.json("/broker/v1/portfolio/scan", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    })
  }

  async runDeltaScan<T = unknown>(body: Record<string, unknown>): Promise<T> {
    return this.json("/broker/v1/portfolio/delta-scan", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    })
  }

  async getPortfolioScan<T = unknown>(scanId: string): Promise<T> {
    return this.json(`/broker/v1/portfolio/scan/${encodeURIComponent(scanId)}`)
  }

  async getPortfolioSummary<T = unknown>(scanId: string): Promise<T> {
    const query = new URLSearchParams({ scanId })
    return this.json(`/broker/v1/portfolio/summary?${query.toString()}`)
  }
}