> ## 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.

# SDK Examples

> Production-ready code patterns for integrating Truthlocks into your applications.

Production-ready code patterns for integrating Truthlocks into your applications. Examples are shown in TypeScript, Go, and Python.

<CardGroup cols={3}>
  <Card title="JavaScript SDK" icon="js" href="/sdk/js">
    JavaScript SDK →
  </Card>

  <Card title="Go SDK" icon="golang" href="/sdk/go">
    Go SDK →
  </Card>

  <Card title="Python SDK" icon="python" href="/sdk/python">
    Python SDK →
  </Card>
</CardGroup>

## End-to-End: Issue and Verify a Credential

Complete workflow from creating an issuer to minting and verifying an attestation.

<CodeGroup>
  ```typescript full-workflow.ts theme={null}
  import { TruthlockClient, Algorithm, Verdict } from '@truthlock/sdk';

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

  // Step 1: Create issuer + register key (one-time setup)
  const issuer = await client.issuers.create({
  name: 'Acme University',
  legal_name: 'Acme University Inc.',
  display_name: 'Acme U',
  });
  await client.issuers.trust(issuer.id);

  await client.keys.register(issuer.id, {
  kid: 'ed-key-2026',
  alg: Algorithm.Ed25519,
  public_key_b64url: 'MCowBQYDK2VwAyEA...', // Your Ed25519 public key
  });

  // Step 2: Mint a degree credential (sends email to recipient)
  const attestation = await client.attestations.mint({
  issuer_id: issuer.id,
  kid: 'ed-key-2026',
  alg: Algorithm.Ed25519,
  schema: 'degree',
  claims: {
  student_name: 'Jane Doe',
  institution: 'Acme University',
  degree_type: 'Bachelor of Science',
  field_of_study: 'Computer Science',
  graduation_date: '2026-05-15',
  honors: 'Magna Cum Laude',
  },
  recipient_email: 'jane.doe@example.com',
  });

  console.log('Minted:', attestation.id, 'Log:', attestation.log_index);

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

  switch (result.verdict) {
  case Verdict.Valid:
  console.log('Valid! Signed by:', result.issuer_name);
  break;
  case Verdict.Revoked:
  console.log('Revoked at:', result.revoked_at);
  break;
  case Verdict.Invalid:
  console.log('Signature invalid or tampered');
  break;
  }

  ```

  ```go full-workflow.go theme={null}
  package main

  import (
      "context"
      "fmt"
      "log"

      truthlock "github.com/truthlocks/sdk-go"
  )

  func main() {
      client := truthlock.NewClient(truthlock.Config{
          BaseURL:  "https://api.truthlocks.com",
          TenantID: "your-tenant-id",
          APIKey:   "tl_live_...",
      })
      ctx := context.Background()

      // Step 1: Create issuer + register key
      issuer, err := client.Issuers.Create(ctx, &truthlock.CreateIssuerRequest{
          Name: "Acme University", LegalName: "Acme University Inc.",
      })
      if err != nil { log.Fatal(err) }
      if _, err := client.Issuers.Trust(ctx, issuer.ID); err != nil { log.Fatal(err) }

      _, err = client.Keys.Register(ctx, issuer.ID, &truthlock.RegisterKeyRequest{
          KID: "ed-key-2026", Alg: truthlock.AlgEd25519,
          PublicKeyB64: "MCowBQYDK2VwAyEA...",
      })
      if err != nil { log.Fatal(err) }

      // Step 2: Mint
      att, err := client.Attestations.Mint(ctx, &truthlock.MintRequest{
          IssuerID: issuer.ID, KID: "ed-key-2026", Alg: truthlock.AlgEd25519,
          Schema: "degree",
          Claims: map[string]interface{}{
              "student_name": "Jane Doe", "institution": "Acme University",
              "degree_type": "Bachelor of Science", "graduation_date": "2026-05-15",
          },
          RecipientEmail: "jane.doe@example.com",
      })
      if err != nil { log.Fatal(err) }
      fmt.Printf("Minted: %s (log: %d)\n", att.ID, att.LogIndex)

      // Step 3: Verify
      result, err := client.Verify.VerifyOnline(ctx, &truthlock.VerifyRequest{
          AttestationID: att.ID,
      })
      if err != nil { log.Fatal(err) }

      switch result.Verdict {
      case truthlock.VerdictValid:
          fmt.Printf("Valid! Signed by: %s\n", result.IssuerName)
      case truthlock.VerdictRevoked:
          fmt.Printf("Revoked at: %s\n", result.RevokedAt)
      case truthlock.VerdictInvalid:
          fmt.Println("Signature invalid or tampered")
      }
  }
  ```

  ```python full_workflow.py theme={null}
  from truthlock import TruthlockClient, Algorithm, Verdict

  client = TruthlockClient(api_key="tl_live_...")

  # Step 1: Create issuer + register key (one-time setup)
  issuer = client.issuers.create(
      name="Acme University",
      legal_name="Acme University Inc.",
      display_name="Acme U",
  )
  client.issuers.trust(issuer.id)

  client.keys.register(
      issuer_id=issuer.id,
      kid="ed-key-2026",
      alg=Algorithm.ED25519.value,
      public_key_b64url="MCowBQYDK2VwAyEA...",  # Your Ed25519 public key
  )

  # Step 2: Mint a degree credential (sends email to recipient)
  attestation = client.attestations.mint(
      issuer_id=issuer.id,
      kid="ed-key-2026",
      alg=Algorithm.ED25519.value,
      schema="degree",
      claims={
          "student_name": "Jane Doe",
          "institution": "Acme University",
          "degree_type": "Bachelor of Science",
          "field_of_study": "Computer Science",
          "graduation_date": "2026-05-15",
          "honors": "Magna Cum Laude",
      },
      recipient_email="jane.doe@example.com",
  )

  print(f"Minted: {attestation.attestation_id} Log: {attestation.log_index}")

  # Step 3: Verify the attestation
  result = client.verify.verify_online(
      attestation_id=attestation.attestation_id,
  )

  if result.verdict == Verdict.VALID:
      print(f"Valid! Signed by: {result.issuer_name}")
  elif result.verdict == Verdict.REVOKED:
      print(f"Revoked at: {result.revoked_at}")
  elif result.verdict == Verdict.INVALID:
      print("Signature invalid or tampered")
  ```
</CodeGroup>

## Revoke and supersede an attestation

Revoke a credential when it's no longer valid, or supersede it with an updated version to preserve the audit trail.

<CodeGroup>
  ```typescript revoke-supersede.ts theme={null}
  import { TruthlockClient, Algorithm, Verdict } from '@truthlock/sdk';

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

  const attestationId = '660e8400-e29b-41d4-a716-446655440001';

  // --- Revoke: permanently invalidate a credential ---
  const revoked = await client.attestations.revoke(attestationId, {
    reason: 'Certificate holder no longer employed',
  });
  console.log('Status:', revoked.status);      // "REVOKED"
  console.log('Revoked at:', revoked.revoked_at);

  // Verify returns REVOKED after revocation
  const check = await client.verify.verifyOnline({ attestation_id: attestationId });
  console.log('Verdict:', check.verdict);      // "REVOKED"

  // --- Supersede: replace with an updated version ---
  const otherAttestationId = '770e8400-e29b-41d4-a716-446655440002';

  const updatedPayload = Buffer.from(JSON.stringify({
    student_name: 'Jane Doe',
    degree_type: 'Master of Science', // Updated from Bachelor
    graduation_date: '2026-05-15',
  })).toString('base64url');

  const result = await client.attestations.supersede(otherAttestationId, {
    new_payload_b64url: updatedPayload,
  });

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

  ```go revoke-supersede.go theme={null}
  package main

  import (
      "context"
      "encoding/base64"
      "encoding/json"
      "fmt"
      "log"

      truthlock "github.com/truthlocks/sdk-go"
  )

  func main() {
      client := truthlock.NewClient(truthlock.Config{
          BaseURL:  "https://api.truthlocks.com",
          TenantID: "your-tenant-id",
          APIKey:   "tl_live_...",
      })
      ctx := context.Background()

      // --- Revoke ---
      revoked, err := client.Attestations.Revoke(ctx, "660e8400-...", &truthlock.RevokeRequest{
          Reason: "Certificate holder no longer employed",
      })
      if err != nil { log.Fatal(err) }
      fmt.Printf("Revoked at: %s\n", revoked.RevokedAt)

      // --- Supersede ---
      claims, _ := json.Marshal(map[string]interface{}{
          "student_name":    "Jane Doe",
          "degree_type":     "Master of Science",
          "graduation_date": "2026-05-15",
      })
      payload := base64.RawURLEncoding.EncodeToString(claims)

      result, err := client.Attestations.Supersede(ctx, "770e8400-...", &truthlock.SupersedeRequest{
          NewPayloadB64URL: payload,
      })
      if err != nil { log.Fatal(err) }
      fmt.Printf("Old: %s (%s)\n", result.Old.AttestationID, result.Old.Status)
      fmt.Printf("New: %s (%s)\n", result.New.AttestationID, result.New.Status)
  }
  ```

  ```python revoke_supersede.py theme={null}
  import base64
  import json
  from truthlock import TruthlockClient, Verdict

  client = TruthlockClient(api_key="tl_live_...")

  attestation_id = "660e8400-e29b-41d4-a716-446655440001"

  # --- Revoke: permanently invalidate a credential ---
  revoked = client.attestations.revoke(
      attestation_id,
      reason="Certificate holder no longer employed",
  )
  print(f"Status: {revoked['status']}")        # "REVOKED"
  print(f"Revoked at: {revoked['revoked_at']}")

  # Verify returns REVOKED after revocation
  check = client.verify.verify_online(attestation_id=attestation_id)
  print(f"Verdict: {check.verdict}")           # "REVOKED"

  # --- Supersede: replace with an updated version ---
  other_attestation_id = "770e8400-e29b-41d4-a716-446655440002"

  updated_payload = base64.urlsafe_b64encode(
      json.dumps({
          "student_name": "Jane Doe",
          "degree_type": "Master of Science",  # Updated from Bachelor
          "graduation_date": "2026-05-15",
      }).encode()
  ).decode().rstrip("=")

  result = client.attestations.supersede(
      other_attestation_id,
      payload_b64url=updated_payload,
  )

  print(f"Old status: {result['old']['status']}")            # "SUPERSEDED"
  print(f"New attestation: {result['new']['attestation_id']}")
  print(f"New status: {result['new']['status']}")            # "VALID"
  ```
</CodeGroup>

<Tip>
  Revocation is permanent and cannot be undone. If you need to issue an updated credential, use **supersede** instead — it preserves the original in the transparency log while linking to the replacement.
</Tip>

## Batch minting with rate limiting

Mint credentials for multiple recipients in a single workflow with configurable concurrency and per-recipient success/failure reporting. Each recipient is processed independently — a failure for one does not block the others.

<CodeGroup>
  ```typescript batch-mint.ts theme={null}
  import { TruthlockClient, Algorithm, TruthlockError } from "@truthlock/sdk";

  interface Recipient {
    name: string;
    email: string;
    department: string;
  }

  async function batchMint(
    client: TruthlockClient,
    issuerId: string,
    recipients: Recipient[],
    concurrency = 10,
  ) {
    const results: { email: string; id?: string; error?: string }[] = [];

    for (let i = 0; i < recipients.length; i += concurrency) {
      const batch = recipients.slice(i, i + concurrency);

      const settled = await Promise.allSettled(
        batch.map((r) =>
          client.attestations.mint({
            issuer_id: issuerId,
            kid: "ed-key-2026",
            alg: Algorithm.Ed25519,
            schema: "employment-verification",
            claims: {
              employee_name: r.name,
              employer: "Acme Corp",
              department: r.department,
              employment_type: "Full-time",
              start_date: new Date().toISOString().split("T")[0],
            },
            recipient_email: r.email,
          }),
        ),
      );

      for (let j = 0; j < settled.length; j++) {
        const s = settled[j];
        results.push({
          email: batch[j].email,
          id: s.status === "fulfilled" ? s.value.id : undefined,
          error: s.status === "rejected" ? s.reason?.message : undefined,
        });
      }
    }

    const ok = results.filter((r) => r.id).length;
    console.log(`Minted ${ok}/${recipients.length} attestations`);
    return results;
  }
  ```

  ```go batch-mint.go theme={null}
  package main

  import (
  	"context"
  	"fmt"
  	"sync"

  	truthlock "github.com/truthlocks/sdk-go"
  )

  type Recipient struct {
  	Name       string
  	Email      string
  	Department string
  }

  type MintResult struct {
  	Email string
  	ID    string
  	Error string
  }

  func batchMint(ctx context.Context, client *truthlock.Client, issuerID string, recipients []Recipient, concurrency int) []MintResult {
  	results := make([]MintResult, len(recipients))
  	sem := make(chan struct{}, concurrency)
  	var wg sync.WaitGroup

  	for i, r := range recipients {
  		wg.Add(1)
  		sem <- struct{}{} // acquire semaphore
  		go func(idx int, recip Recipient) {
  			defer wg.Done()
  			defer func() { <-sem }() // release semaphore

  			att, err := client.Attestations.Mint(ctx, &truthlock.MintRequest{
  				IssuerID: issuerID,
  				KID:      "ed-key-2026",
  				Alg:      truthlock.AlgEd25519,
  				Schema:   "employment-verification",
  				Claims: map[string]interface{}{
  					"employee_name":   recip.Name,
  					"employer":        "Acme Corp",
  					"department":      recip.Department,
  					"employment_type": "Full-time",
  				},
  				RecipientEmail: recip.Email,
  			})
  			if err != nil {
  				results[idx] = MintResult{Email: recip.Email, Error: err.Error()}
  			} else {
  				results[idx] = MintResult{Email: recip.Email, ID: att.ID}
  			}
  		}(i, r)
  	}

  	wg.Wait()

  	ok := 0
  	for _, r := range results {
  		if r.ID != "" {
  			ok++
  		}
  	}
  	fmt.Printf("Minted %d/%d attestations\n", ok, len(recipients))
  	return results
  }
  ```

  ```python batch_mint.py theme={null}
  import asyncio
  from dataclasses import dataclass
  from truthlock import TruthlockClient, Algorithm

  client = TruthlockClient(api_key="tl_live_...")

  @dataclass
  class Recipient:
      name: str
      email: str
      department: str

  @dataclass
  class MintResult:
      email: str
      id: str | None = None
      error: str | None = None

  async def batch_mint(
      issuer_id: str,
      recipients: list[Recipient],
      concurrency: int = 10,
  ) -> list[MintResult]:
      semaphore = asyncio.Semaphore(concurrency)
      results: list[MintResult] = []

      async def mint_one(recip: Recipient) -> MintResult:
          async with semaphore:
              try:
                  att = client.attestations.mint(
                      issuer_id=issuer_id,
                      kid="ed-key-2026",
                      alg=Algorithm.ED25519.value,
                      schema="employment-verification",
                      claims={
                          "employee_name": recip.name,
                          "employer": "Acme Corp",
                          "department": recip.department,
                          "employment_type": "Full-time",
                      },
                      recipient_email=recip.email,
                  )
                  return MintResult(email=recip.email, id=att.attestation_id)
              except Exception as e:
                  return MintResult(email=recip.email, error=str(e))

      results = await asyncio.gather(*(mint_one(r) for r in recipients))
      ok = sum(1 for r in results if r.id)
      print(f"Minted {ok}/{len(recipients)} attestations")
      return list(results)
  ```
</CodeGroup>

<Tip>
  Set `concurrency` to match your plan's rate limit. Start with 10 for Starter plans and increase to 50 or higher on Business and Enterprise tiers. Each failed recipient includes a descriptive error so you can retry selectively.
</Tip>

## Query audit logs

Retrieve and filter audit events for security monitoring and compliance reporting.

<CodeGroup>
  ```typescript audit-query.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" },
  });

  // Find all attestation operations for the past month
  const events = await client.audit.query({
    resource_type: "attestation",
    from: "2026-01-01T00:00:00Z",
    to: "2026-01-31T23:59:59Z",
    limit: 200,
  });

  for (const event of events) {
    console.log(
      `[${event.timestamp}] ${event.action} on ${event.resource_id} by ${event.actor_id}`,
    );
  }

  // Export a compliance report as CSV
  const exportJob = await client.audit.export({
    start_date: "2026-01-01",
    end_date: "2026-01-31",
    format: "csv",
  });
  console.log(`Export job: ${exportJob.id} (${exportJob.status})`);
  ```

  ```go audit-query.go theme={null}
  package main

  import (
  	"context"
  	"fmt"
  	"log"

  	truthlock "github.com/truthlocks/sdk-go"
  )

  func main() {
  	client := truthlock.NewClient(truthlock.Config{
  		BaseURL:  "https://api.truthlocks.com",
  		TenantID: "your-tenant-id",
  		APIKey:   "tl_live_...",
  	})
  	ctx := context.Background()

  	events, err := client.Audit.Query(ctx, map[string]string{
  		"resource_type": "attestation",
  		"from":          "2026-01-01T00:00:00Z",
  		"to":            "2026-01-31T23:59:59Z",
  		"limit":         "200",
  	})
  	if err != nil {
  		log.Fatal(err)
  	}

  	for _, e := range events {
  		fmt.Printf("[%s] %s on %s by %s\n", e.Timestamp, e.Action, e.ResourceID, e.ActorID)
  	}

  	exportJob, err := client.Audit.Export(ctx, &truthlock.AuditExportRequest{
  		StartDate: "2026-01-01",
  		EndDate:   "2026-01-31",
  		Format:    "csv",
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	fmt.Printf("Export job: %s (%s)\n", exportJob.ID, exportJob.Status)
  }
  ```

  ```python audit_query.py theme={null}
  from truthlock import TruthlockClient

  client = TruthlockClient(api_key="tl_live_...")

  # Find all attestation operations for the past month
  events = client.audit.query(
      resource_type="attestation",
      from_date="2026-01-01T00:00:00Z",
      to_date="2026-01-31T23:59:59Z",
      limit=200,
  )

  for event in events:
      print(f"[{event['timestamp']}] {event['action']} on {event['resource_id']} by {event['actor_id']}")

  # Export a compliance report as CSV
  export_job = client.audit.export(
      start_date="2026-01-01",
      end_date="2026-01-31",
      format="csv",
  )
  print(f"Export job: {export_job['id']} ({export_job['status']})")
  ```
</CodeGroup>

<Tip>
  Use `resource_type` and `action` filters to scope queries. For long-term archival, schedule periodic exports and store them in your own infrastructure. See [audit logs](/security/audit) for retention tiers and SIEM integration options.
</Tip>

## Governance workflow

Create a multi-party approval workflow to manage issuer lifecycle changes such as suspend, revoke, and reinstate.

<CodeGroup>
  ```typescript governance-workflow.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" },
  });

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

  // Step 2: Approve (requires governance:approve permission)
  await client.governance.approveRequest(req.id);

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

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

  ```go governance-workflow.go theme={null}
  package main

  import (
  	"context"
  	"fmt"
  	"log"

  	truthlock "github.com/truthlocks/sdk-go"
  )

  func main() {
  	client := truthlock.NewClient(truthlock.Config{
  		BaseURL:  "https://api.truthlocks.com",
  		TenantID: "your-tenant-id",
  		APIKey:   "tl_live_...",
  	})
  	ctx := context.Background()

  	// Step 1: Create a request to suspend an issuer
  	req, err := client.Governance.CreateRequest(ctx, &truthlock.GovernanceRequest{
  		Action:   "suspend",
  		IssuerID: "issuer-uuid",
  		Reason:   "Annual compliance review",
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	fmt.Printf("Request %s: %s\n", req.ID, req.Status)

  	// Step 2: Approve
  	if _, err := client.Governance.ApproveRequest(ctx, req.ID); err != nil {
  		log.Fatal(err)
  	}

  	// Step 3: Execute
  	result, err := client.Governance.ExecuteRequest(ctx, req.ID)
  	if err != nil {
  		log.Fatal(err)
  	}
  	fmt.Printf("Issuer is now: %s\n", result.IssuerStatus)

  	// List all pending requests
  	requests, err := client.Governance.ListRequests(ctx)
  	if err != nil {
  		log.Fatal(err)
  	}
  	for _, r := range requests {
  		fmt.Printf("[%s] %s on %s\n", r.Status, r.Action, r.IssuerID)
  	}
  }
  ```

  ```python governance_workflow.py theme={null}
  from truthlock import TruthlockClient

  client = TruthlockClient(api_key="tl_live_...")

  # Step 1: Create a request to suspend an issuer
  req = client.governance.create_request(
      action="suspend",
      issuer_id="issuer-uuid",
      reason="Annual compliance review",
  )
  print(f"Request {req['id']}: {req['status']}")

  # Step 2: Approve (requires governance:approve permission)
  client.governance.approve_request(req["id"])

  # Step 3: Execute the approved request
  result = client.governance.execute_request(req["id"])
  print(f"Issuer is now: {result['issuer_status']}")

  # List all pending requests
  pending = client.governance.list_requests()
  for r in pending:
      print(f"[{r['status']}] {r['action']} on {r['issuer_id']}")
  ```
</CodeGroup>

<Tip>
  Governance requests require different permissions at each step. Creating requests needs `governance:create`, approving needs `governance:approve`, and executing needs `governance:execute`. See [RBAC](/security/rbac) for role configuration.
</Tip>

## Webhook Signature Verification

Securely verify incoming webhooks from Truthlocks to ensure they haven't been tampered with.

```typescript webhook-handler.ts theme={null}
import { verifyWebhookSignature } from "@truthlock/sdk";
import express from "express";

const app = express();
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/truthlock", (req, res) => {
  const signature = req.headers["x-truthlocks-signature"] as string;
  const isValid = verifyWebhookSignature(
    req.body,
    signature,
    process.env.WEBHOOK_SECRET!,
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(req.body.toString());

  switch (event.type) {
    case "attestation.created":
      console.log("New attestation:", event.data.id);
      // Update your database, notify users, etc.
      break;

    case "attestation.revoked":
      console.log("Revoked:", event.data.id, "Reason:", event.data.reason);
      // Invalidate cached verification results
      break;

    case "issuer.created":
      console.log("Issuer created:", event.data.issuer_id);
      break;
  }

  res.json({ received: true });
});
```

## Caching Verification Results

Cache verification results to reduce API calls. Only cache VALID results — revocations should always be checked fresh.

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

// Simple in-memory cache with TTL
const cache = new Map<string, { result: any; expires: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function verifyWithCache(client: TruthlockClient, attestationId: string) {
  // Check cache first
  const cached = cache.get(attestationId);
  if (cached && cached.expires > Date.now()) {
    return cached.result;
  }

  // Fetch fresh result
  const result = await client.verify.verifyOnline({
    attestation_id: attestationId,
  });

  // Only cache VALID results (revocations must be checked each time)
  if (result.verdict === Verdict.Valid) {
    cache.set(attestationId, {
      result,
      expires: Date.now() + CACHE_TTL,
    });
  }

  return result;
}
```

## Document Attestation with SHA-256

Attest a PDF document by computing its SHA-256 hash and including it in the claims for integrity verification.

<CodeGroup>
  ```typescript document-attest.ts theme={null}
  import { createHash } from 'crypto';
  import { readFile } from 'fs/promises';
  import { TruthlockClient, Algorithm } from '@truthlock/sdk';

  async function attestDocument(
  client: TruthlockClient,
  issuerId: string,
  filePath: string,
  ) {
  const fileBuffer = await readFile(filePath);
  const sha256 = createHash('sha256').update(fileBuffer).digest('hex');
  const fileName = filePath.split('/').pop() || 'document';

  const attestation = await client.attestations.mint({
  issuer_id: issuerId,
  kid: 'ed-key-2026',
  alg: Algorithm.Ed25519,
  schema: 'custom',
  content_type: 'application/pdf',
  claims: {
  document: {
  sha256,
  name: fileName,
  size: fileBuffer.length,
  content_type: 'application/pdf',
  },
  subject: 'Employment Contract',
  signer: 'Jane Doe',
  signed_date: new Date().toISOString().split('T')[0],
  },
  });

  console.log('Document attested:', attestation.id);
  console.log('SHA-256:', sha256);
  return attestation;
  }

  ```

  ```go document-attest.go theme={null}
  func attestDocument(ctx context.Context, client *truthlock.Client, issuerId, filePath string) error {
      data, err := os.ReadFile(filePath)
      if err != nil {
          return fmt.Errorf("read file: %w", err)
      }

      hash := sha256.Sum256(data)
      hashHex := hex.EncodeToString(hash[:])

      att, err := client.Attestations.Mint(ctx, &truthlock.MintRequest{
          IssuerID:    issuerId,
          KID:         "ed-key-2026",
          Alg:         truthlock.AlgEd25519,
          Schema:      "custom",
          ContentType: "application/pdf",
          Claims: map[string]interface{}{
              "document": map[string]interface{}{
                  "sha256":       hashHex,
                  "name":         filepath.Base(filePath),
                  "size":         len(data),
                  "content_type": "application/pdf",
              },
              "subject":     "Employment Contract",
              "signer":      "Jane Doe",
              "signed_date": time.Now().Format("2006-01-02"),
          },
      })
      if err != nil {
          return fmt.Errorf("mint: %w", err)
      }

      fmt.Printf("Document attested: %s (SHA-256: %s)\n", att.ID, hashHex)
      return nil
  }
  ```

  ```python document_attest.py theme={null}
  import hashlib
  import os
  from datetime import date
  from truthlock import TruthlockClient, Algorithm

  client = TruthlockClient(api_key="tl_live_...")

  def attest_document(issuer_id: str, file_path: str):
      with open(file_path, "rb") as f:
          data = f.read()

      sha256 = hashlib.sha256(data).hexdigest()
      file_name = os.path.basename(file_path)

      attestation = client.attestations.mint(
          issuer_id=issuer_id,
          kid="ed-key-2026",
          alg=Algorithm.ED25519.value,
          schema="custom",
          content_type="application/pdf",
          claims={
              "document": {
                  "sha256": sha256,
                  "name": file_name,
                  "size": len(data),
                  "content_type": "application/pdf",
              },
              "subject": "Employment Contract",
              "signer": "Jane Doe",
              "signed_date": date.today().isoformat(),
          },
      )

      print(f"Document attested: {attestation.attestation_id}")
      print(f"SHA-256: {sha256}")
      return attestation
  ```
</CodeGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Mint API reference" icon="server" href="/api-reference/attestations/mint">
    Full API docs with 35 credential schemas and interactive playground.
  </Card>

  <Card title="JavaScript SDK" icon="js" href="/sdk/js">
    Installation, authentication, and full method reference.
  </Card>

  <Card title="Go SDK" icon="golang" href="/sdk/go">
    Idiomatic Go with context propagation and typed structs.
  </Card>

  <Card title="Python SDK" icon="python" href="/sdk/python">
    Python SDK with async support and type hints.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/ops/limits">
    Understand quotas and retry strategies for production.
  </Card>

  <Card title="Revoke API reference" icon="ban" href="/api-reference/attestations/revoke">
    Permanently invalidate an attestation.
  </Card>

  <Card title="Supersede API reference" icon="arrow-up-from-bracket" href="/api-reference/attestations/supersede">
    Replace an attestation with an updated version.
  </Card>
</CardGroup>
