JavaScript SDK
JavaScript SDK →
Go SDK
Go SDK →
Python SDK
Python SDK →
End-to-End: Issue and Verify a Credential
Complete workflow from creating an issuer to minting and verifying an attestation.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;
}
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")
}
}
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")
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.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"
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)
}
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"
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.
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.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;
}
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
}
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)
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.Query audit logs
Retrieve and filter audit events for security monitoring and compliance reporting.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})`);
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)
}
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']})")
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 for retention tiers and SIEM integration options.Governance workflow
Create a multi-party approval workflow to manage issuer lifecycle changes such as suspend, revoke, and reinstate.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}`);
}
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)
}
}
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']}")
Governance requests require different permissions at each step. Creating requests needs
governance:create, approving needs governance:approve, and executing needs governance:execute. See RBAC for role configuration.Webhook Signature Verification
Securely verify incoming webhooks from Truthlocks to ensure they haven’t been tampered with.webhook-handler.ts
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.cached-verify.ts
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.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;
}
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
}
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
Next Steps
Mint API reference
Full API docs with 35 credential schemas and interactive playground.
JavaScript SDK
Installation, authentication, and full method reference.
Go SDK
Idiomatic Go with context propagation and typed structs.
Python SDK
Python SDK with async support and type hints.
Rate limits
Understand quotas and retry strategies for production.
Revoke API reference
Permanently invalidate an attestation.
Supersede API reference
Replace an attestation with an updated version.

