Skip to content

Decision Records

Every Vettly judgment produces a decision record—a complete audit trail that can be retrieved by ID at any time.

Anatomy of a Decision

When you call vettly.check(), the response includes:

typescript
interface Decision {
  // Unique identifier for this decision
  id: string // "dec_abc123def456..."

  // The action taken
  action: 'allow' | 'warn' | 'flag' | 'block'

  // Policy information
  policy: {
    id: string      // "community-safe"
    version: string // "v2.3.1"
  }

  // What triggered the action
  triggered: {
    category: string    // "harassment"
    threshold: number   // 0.4
    score: number       // 0.67
  }[]

  // Raw provider response
  provider: {
    name: string        // "openai"
    model: string       // "omni-moderation-latest"
    scores: Record<string, number>
  }

  // Input verification
  input: {
    hash: string        // SHA-256 of content
    type: 'text' | 'image' | 'video'
  }

  // Context you provided
  context: {
    userId?: string
    sessionId?: string
    ipAddress?: string
    custom?: Record<string, unknown>
  }

  // Timestamps
  createdAt: string // ISO 8601
}

Why Decision IDs Matter

For Appeals

When a user contests a decision:

typescript
// User clicks "Appeal this decision"
const decision = await vettly.getDecision(decisionId)

// Show them what happened
console.log(`
  Your content was ${decision.action}ed because:
  - Category: ${decision.triggered[0].category}
  - Score: ${decision.triggered[0].score}
  - Threshold: ${decision.triggered[0].threshold}
`)

For Audits

When compliance needs a report:

typescript
// Export all decisions for a time period
const decisions = await vettly.listDecisions({
  from: '2024-01-01',
  to: '2024-01-31',
  action: 'block'
})

// Generate compliance report
const report = decisions.map(d => ({
  id: d.id,
  action: d.action,
  category: d.triggered[0]?.category,
  timestamp: d.createdAt
}))

When lawyers need evidence:

typescript
// Retrieve specific decision for litigation
const decision = await vettly.getDecision(decisionId)

// Verify content hasn't changed
const currentHash = sha256(contentFromDb)
if (currentHash !== decision.input.hash) {
  console.log('Content has been modified since decision')
}

// Show complete evidence chain
console.log({
  contentHash: decision.input.hash,
  policyVersion: decision.policy.version,
  providerResponse: decision.provider,
  actionTaken: decision.action,
  timestamp: decision.createdAt
})

Storing Decision IDs

Always store the decision ID alongside your content:

typescript
// When creating content
const decision = await vettly.check({ content: post.body })

await db.posts.create({
  data: {
    body: post.body,
    authorId: user.id,
    moderationDecisionId: decision.id, // Store this!
    moderationAction: decision.action
  }
})
typescript
// When updating content
const decision = await vettly.check({ content: updatedBody })

await db.posts.update({
  where: { id: post.id },
  data: {
    body: updatedBody,
    moderationDecisionId: decision.id, // New decision for new content
    moderationAction: decision.action
  }
})

Input Hashing

Every decision includes a SHA-256 hash of the input content:

typescript
const decision = await vettly.check({ content: 'Hello world' })
console.log(decision.input.hash)
// "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"

This hash allows you to:

  1. Verify content integrity - Prove the decision was made on specific content
  2. Detect modifications - Know if content changed after the decision
  3. Support non-repudiation - Content author can't claim different content was evaluated

Retention and Deletion

Default Retention

Decisions are retained based on your plan:

PlanRetention
Developer24 hours
Starter7 days
Growth30 days
Pro90 days

For litigation, you can extend retention:

typescript
await vettly.setLegalHold({
  decisionIds: ['dec_abc...', 'dec_def...'],
  reason: 'Litigation hold - Case #12345',
  expiry: '2025-12-31'
})

GDPR Deletion

For right-to-erasure requests:

typescript
await vettly.deleteUserDecisions({
  userId: 'user_123',
  reason: 'GDPR erasure request'
})

Next Steps