> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truthlocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript / TypeScript SDK

> Official TypeScript/JavaScript SDK for the Truthlocks platform. Fully typed, with automatic authentication, idempotency keys, retry with exponential backoff, and tree-shakeable exports.

<Info>
  **Public package:** The `@truthlock/sdk` package is available on the public
  npm registry. No special configuration required.
</Info>

## Installation

<CodeGroup>
  `bash npm npm install @truthlock/sdk ` `bash yarn yarn add
      @truthlock/sdk ` `bash pnpm pnpm add @truthlock/sdk `
</CodeGroup>

## Quick Start

```typescript quickstart.ts theme={null}
import { TruthlockClient, Algorithm, Verdict } from "@truthlock/sdk";

// 1. Create client
const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: {
    type: "apiKey",
    apiKey: "tl_live_...",
    tenantId: "your-tenant-id",
  },
});

// 2. Create and trust an issuer
const issuer = await client.issuers.create({
  name: "My Organization",
  legal_name: "My Organization Inc.",
  display_name: "My Org",
});
await client.issuers.trust(issuer.id);

// 3. Register a signing key
await client.keys.register(issuer.id, {
  kid: "key-2026",
  alg: Algorithm.Ed25519,
  public_key_b64url: "your-public-key-base64url",
});

// 4. Mint an attestation
const attestation = await client.attestations.mint({
  issuer_id: issuer.id,
  kid: "key-2026",
  alg: Algorithm.Ed25519,
  schema: "degree",
  claims: {
    student_name: "Jane Doe",
    degree_type: "Bachelor of Science",
  },
});

console.log("Attestation ID:", attestation.id);

// 5. Verify
const result = await client.verify.verifyOnline({
  attestation_id: attestation.id,
});

if (result.verdict === Verdict.Valid) {
  console.log("Attestation verified successfully");
}
```

## Revoke an attestation

Permanently invalidate an attestation. Once revoked, any verification check returns `REVOKED`. This action cannot be undone — if you need to issue an updated credential instead, use [supersede](#supersede-an-attestation).

```typescript revoke.ts theme={null}
import { TruthlockClient, Verdict } from "@truthlock/sdk";

const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "your-tenant-id" },
});

const revoked = await client.attestations.revoke(
  "660e8400-e29b-41d4-a716-446655440001",
  { reason: "Certificate holder no longer employed" }
);

console.log("Status:", revoked.status); // "REVOKED"
console.log("Revoked at:", revoked.revoked_at);

// Subsequent verifications reflect the revocation
const check = await client.verify.verifyOnline({
  attestation_id: "660e8400-e29b-41d4-a716-446655440001",
});
console.log("Verdict:", check.verdict); // "REVOKED"
```

<Warning>
  Revocation is permanent and recorded in the transparency log. You cannot undo it. Use **supersede** if you need to replace a credential with an updated version.
</Warning>

## Supersede an attestation

Replace an existing attestation with an updated version. The original is marked as `SUPERSEDED` and linked to the new one, creating an auditable version chain.

```typescript supersede.ts theme={null}
import { TruthlockClient, Algorithm } from "@truthlock/sdk";

const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "your-tenant-id" },
});

// Supersede an attestation with updated claims
const updatedPayload = Buffer.from(
  JSON.stringify({
    student_name: "Jane Doe",
    degree_type: "Master of Science", // Updated from Bachelor to Master
  })
).toString("base64url");

const result = await client.attestations.supersede(
  "660e8400-e29b-41d4-a716-446655440001", // Original attestation ID
  { new_payload_b64url: updatedPayload }
);

console.log("Old attestation status:", result.old.status); // "SUPERSEDED"
console.log("New attestation ID:", result.new.attestation_id);
```

<Info>
  Both the original and new attestation remain in the transparency log. Verifiers can trace the full chain using the `superseded_by_attestation_id` field on the original.
</Info>

## Configuration

```typescript theme={null}
const client = new TruthlockClient({
  // Required
  baseUrl: 'https://api.truthlocks.com',  // API base URL
  auth: { ... },                           // See Authentication below

  // Optional
  timeout: 30_000,              // Request timeout in ms (default: 30s)
  maxRetries: 3,                // Auto-retry with exponential backoff
  idempotencyPrefix: 'my-app',  // Prefix for auto-generated idempotency keys
});
```

## Idempotency keys

The SDK generates an `Idempotency-Key` header automatically on every write operation (mint, revoke, supersede), making retries safe by default. If a request fails and is retried, the server returns the original response instead of performing the action twice.

When multiple applications share the same tenant, set `idempotencyPrefix` to namespace the auto-generated keys and prevent collisions:

```typescript theme={null}
// Service A
const billingClient = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "shared-tenant-id" },
  idempotencyPrefix: "billing-svc",
});

// Service B
const onboardingClient = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "shared-tenant-id" },
  idempotencyPrefix: "onboarding-svc",
});
```

With these prefixes, a key generated by the billing service looks like `billing-svc_<uuid>`, while onboarding produces `onboarding-svc_<uuid>` — so concurrent requests from different services never conflict.

<Info>
  Idempotency keys expire after 24 hours. If you omit `idempotencyPrefix`, keys are generated without a prefix and are still unique per request.
</Info>

## Authentication

Three authentication methods are supported. API Key is recommended for server-side applications.

```typescript API Key (recommended) theme={null}
auth: {
  type: 'apiKey',
  apiKey: 'tl_live_...',     // From Console > Settings > API Keys
  tenantId: 'your-tenant-id', // From Console > Settings > General
}
```

```typescript Bearer Token (session-based) theme={null}
auth: {
  type: 'bearer',
  token: 'eyJhbGciOi...',  // JWT from login flow
}
```

```typescript Service Key (machine-to-machine) theme={null}
auth: {
  type: 'service',
  apiKey: 'tl_svc_...',
  tenantId: 'your-tenant-id',
}
```

## Retry behavior

When `maxRetries` is set (default: 3), the SDK automatically retries failed requests using exponential backoff with jitter.

### What gets retried

The SDK retries a request when **all** of the following are true:

* The HTTP status code is retryable: `408`, `429`, `500`, `502`, `503`, or `504`
* The retry count has not exceeded `maxRetries`
* The request has not been aborted via an `AbortController`

Responses with `400`, `401`, `403`, `404`, or `409` are **not** retried.

### Backoff schedule

| Attempt   | Base delay | With jitter (approx.) |
| :-------- | :--------- | :-------------------- |
| 1st retry | 100 ms     | 80–120 ms             |
| 2nd retry | 200 ms     | 160–240 ms            |
| 3rd retry | 400 ms     | 320–480 ms            |

The delay doubles on each attempt, capped at 2 seconds. Jitter adds ±20% randomization.

When the API returns a `429` with a `Retry-After` header, the SDK waits the server-specified duration instead.

### Disabling retries

```typescript theme={null}
const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "your-tenant-id" },
  maxRetries: 0, // No automatic retries
});
```

<Info>
  The SDK generates idempotency keys automatically for write operations, so retries for mint and revoke calls are safe. You can set a custom prefix with the `idempotencyPrefix` option.
</Info>

## Error handling

```typescript theme={null}
import { TruthlockClient, TruthlockError } from "@truthlock/sdk";

try {
  const attestation = await client.attestations.mint(data);
} catch (error) {
  if (error instanceof TruthlockError) {
    console.log("Code:", error.code); // e.g. "ISSUER_NOT_TRUSTED"
    console.log("Status:", error.status); // HTTP status code
    console.log("Message:", error.message); // Human-readable

    if (error.code === "RATE_LIMITED") {
      // Already retried maxRetries times — back off at the application level
    }
  }
  throw error;
}
```

## API methods

All methods return typed Promises.

### Issuers

* `client.issuers.create(data)`
* `client.issuers.get(id)`
* `client.issuers.list()`
* `client.issuers.trust(id)`
* `client.issuers.suspend(id)`
* `client.issuers.revoke(id, reason)`

### Keys

* `client.keys.register(issuerId, data)`
* `client.keys.list(issuerId)`
* `client.keys.rotate(kid, data)`
* `client.keys.reportCompromise(kid)`

### Attestations

* `client.attestations.mint(data)`
* `client.attestations.get(id)`
* `client.attestations.list()`
* `client.attestations.revoke(id, data)`
* `client.attestations.supersede(id, data)` — [see supersede API](/api-reference/attestations/supersede)
* `client.attestations.getProofBundle(id)`

### Receipts

* `client.receipts.mint(data)`
* `client.receipts.get(id)`
* `client.receipts.list(filter?)`
* `client.receipts.revoke(id, data?)`
* `client.receipts.listTypes()`
* `client.receipts.getType(name)`
* `client.receipts.createType(data)`
* `client.receipts.getProofBundle(id)` — [see proof bundle API](/api-reference/receipts/proof-bundle)
* `client.receipts.verify(receiptId)` — [see verify API](/api-reference/receipts/verify)
* `client.receipts.search(query)` — [see search API](/api-reference/receipts/search)
* `client.receipts.export(req)` — [see export API](/api-reference/receipts/export)
* `client.receipts.getExport(exportId)`
* `client.receipts.listExports()`
* `client.receipts.redact(id)` — [see redact API](/api-reference/receipts/redact)

### Verification

* `client.verify.verifyOnline(data)`

### API Keys

* `client.apiKeys.list()`
* `client.apiKeys.create(data)`
* `client.apiKeys.revoke(id)`

### Audit

* `client.audit.query(params)`
* `client.audit.export(data)`

### Governance

* `client.governance.listRequests()`
* `client.governance.createRequest(data)`
* `client.governance.approveRequest(id)`
* `client.governance.executeRequest(id)`

## Governance workflows

Manage formal issuer actions — suspend, revoke, reinstate, and change trust tier — through a multi-party approval workflow. Create a request, collect approvals from authorized reviewers, then execute.

```typescript governance.ts theme={null}
import { TruthlockClient } from "@truthlock/sdk";

const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "your-tenant-id" },
});

// Create a governance request to suspend an issuer
const req = await client.governance.createRequest({
  action: "suspend",
  issuer_id: "issuer-uuid",
  reason: "Compliance review pending",
});
console.log(`Request created: ${req.id} (status: ${req.status})`);

// Another authorized user approves the request
await client.governance.approveRequest(req.id);

// Execute the approved request
const result = await client.governance.executeRequest(req.id);
console.log(`Executed: ${result.id} — issuer is now ${result.issuer_status}`);

// List all governance requests
const requests = await client.governance.listRequests();
for (const r of requests) {
  console.log(`[${r.status}] ${r.action} ${r.issuer_id} — ${r.reason}`);
}
```

See the [governance API reference](/api-reference/governance/list-requests) for the full request and response schemas.

## Audit queries and exports

Query audit events with filters and export logs for compliance reporting. Use `client.audit.query()` to search events and `client.audit.export()` to start an asynchronous export job.

```typescript audit.ts theme={null}
import { TruthlockClient } from "@truthlock/sdk";

const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "your-tenant-id" },
});

// Query recent attestation events
const events = await client.audit.query({
  action: "attestation.mint",
  from: "2026-06-01T00:00:00Z",
  to: "2026-06-30T23:59:59Z",
  limit: 100,
});
for (const evt of events.data) {
  console.log(`[${evt.timestamp}] ${evt.action} by ${evt.actor_id}`);
}

// Start an async export for compliance
const exportJob = await client.audit.export({
  from: "2026-01-01T00:00:00Z",
  to: "2026-06-30T23:59:59Z",
  report_type: "soc2", // "soc2", "gdpr", "hipaa", or omit for raw
  format: "json",
});
console.log(`Export started: ${exportJob.id} (status: ${exportJob.status})`);
```

See [audit logs](/security/audit) for the full event structure, filter parameters, and retention policies.

## Receipt operations

Mint, verify, search, export, and redact cryptographically signed receipts. See the [receipts guide](/guides/receipts) for an overview of receipt types and the full lifecycle.

```typescript receipts.ts theme={null}
import { TruthlockClient } from "@truthlock/sdk";

const client = new TruthlockClient({
  baseUrl: "https://api.truthlocks.com",
  auth: { type: "apiKey", apiKey: "tl_live_...", tenantId: "your-tenant-id" },
});

// Mint a payment receipt
const receipt = await client.receipts.mint({
  issuer_id: "your-issuer-id",
  kid: "ed-key-2026",
  alg: "Ed25519",
  receipt_type: "payment_receipt",
  subject: "customer@example.com",
  payload: {
    amount: 5000,
    currency: "USD",
    provider: "stripe",
    provider_reference: "ch_3Px...",
  },
});

// Verify a receipt
const result = await client.receipts.verify(receipt.receipt_id);
console.log("Verdict:", result.verdict); // "VALID"

// Download a proof bundle for offline verification
const bundle = await client.receipts.getProofBundle(receipt.receipt_id);

// Search receipts
const results = await client.receipts.search({
  q: "stripe",
  receipt_type: "payment_receipt",
  from_date: "2026-01-01T00:00:00Z",
});

// Export receipts as JSON
const exportJob = await client.receipts.export({
  format: "json",
  filters: { receipt_type: "payment_receipt" },
});
// Poll for completion
const completed = await client.receipts.getExport(exportJob.id);

// Redact PII from a receipt (permanent, for GDPR erasure requests)
await client.receipts.redact(receipt.receipt_id);
```

See the [receipts API reference](/api-reference/receipts/mint) for the full request and response schemas.
