Skip to content

Appeals Workflow

Vettly provides built-in infrastructure for handling contested decisions. When users believe a decision was wrong, they deserve a fair process with complete transparency.

Why Appeals Matter

Appeals serve multiple purposes:

  1. Due process - Users deserve to contest decisions that affect them
  2. Policy tuning - Appeals reveal false positives and threshold issues
  3. Legal protection - Documented appeal processes protect against litigation
  4. Regulatory compliance - DSA Article 20 requires effective complaint handling

Appeal Lifecycle

Decision Made → User Appeals → Review Queue → Human Review → Resolution

                                            Re-evaluation
                                            (optional)

Initiating Appeals

User-Facing Appeal

typescript
// User submits an appeal
const appeal = await vettly.createAppeal({
  decisionId: 'dec_abc123',
  userId: 'user_123',
  reason: 'This was a joke between friends, not harassment',
  contactEmail: 'user@example.com'
})

// appeal.id = "app_xyz789"
// appeal.status = "pending"

Appeal with Evidence

typescript
const appeal = await vettly.createAppeal({
  decisionId: 'dec_abc123',
  userId: 'user_123',
  reason: 'Context shows this was sarcasm',
  evidence: [
    {
      type: 'url',
      description: 'Previous message showing context',
      url: 'https://platform.com/message/456'
    }
  ]
})

Review Queue

Listing Appeals

typescript
// Get pending appeals
const pending = await vettly.listAppeals({
  status: 'pending',
  sortBy: 'createdAt',
  order: 'asc' // Oldest first
})

// Filter by category
const harassmentAppeals = await vettly.listAppeals({
  status: 'pending',
  category: 'harassment'
})

// Filter by priority (based on user history, content severity)
const highPriority = await vettly.listAppeals({
  status: 'pending',
  priority: 'high'
})

Appeal Details

typescript
const appeal = await vettly.getAppeal('app_xyz789')

// Returns:
{
  id: 'app_xyz789',
  status: 'pending',
  createdAt: '2024-01-15T10:00:00Z',

  // Original decision
  decision: {
    id: 'dec_abc123',
    action: 'block',
    policy: { id: 'community-safe', version: '2.3.1' },
    triggered: [{ category: 'harassment', score: 0.67, threshold: 0.4 }]
  },

  // User's appeal
  user: {
    id: 'user_123',
    reason: 'This was a joke between friends',
    evidence: []
  },

  // Content (if preserved)
  content: {
    type: 'text',
    value: 'Original content here'
  }
}

Processing Appeals

Human Review

typescript
// Reviewer examines the appeal and makes a decision
const resolution = await vettly.resolveAppeal({
  appealId: 'app_xyz789',
  reviewerId: 'admin@company.com',
  decision: 'upheld', // or 'overturned'
  reason: 'Content violates community guidelines, decision stands',
  notes: 'Reviewed context, but content is clearly targeted harassment'
})

Overturning a Decision

typescript
const resolution = await vettly.resolveAppeal({
  appealId: 'app_xyz789',
  reviewerId: 'admin@company.com',
  decision: 'overturned',
  reason: 'Content was clearly satirical when viewed in context',
  newAction: 'allow', // Change the action
  restoreContent: true // Restore blocked content
})

Re-evaluation

If policy has changed since the original decision:

typescript
// Check if policy has changed
const comparison = await vettly.comparePolicyVersions({
  policy: 'community-safe',
  from: appeal.decision.policy.version,
  to: 'latest',
  decisionId: appeal.decision.id
})

if (comparison.changed) {
  // Re-evaluate under current policy
  const reevaluated = await vettly.reevaluateForAppeal({
    appealId: 'app_xyz789',
    policyVersion: 'latest'
  })

  console.log({
    originalAction: appeal.decision.action,
    newAction: reevaluated.action,
    reason: comparison.reason
  })
}

User Notification

Appeal Received

typescript
// Automatic notification when appeal is created
await vettly.setAppealNotifications({
  onReceived: {
    template: 'appeal-received',
    channel: 'email'
  }
})

Appeal Resolved

typescript
await vettly.setAppealNotifications({
  onResolved: {
    template: 'appeal-resolved',
    channel: 'email',
    includeDecisionDetails: true
  }
})

Notification Templates

typescript
await vettly.setNotificationTemplate({
  id: 'appeal-resolved',
  subject: 'Your appeal has been reviewed',
  body: `
    Hello,

    Your appeal regarding the {{decision.action}} of your content has been reviewed.

    Decision: {{resolution.decision}}
    Reason: {{resolution.reason}}

    {{#if resolution.decision == 'overturned'}}
    Your content has been restored.
    {{else}}
    If you believe this decision is still incorrect, you may contact us at appeals@platform.com
    {{/if}}
  `
})

Analytics

Appeal Metrics

typescript
const metrics = await vettly.getAppealMetrics({
  from: '2024-01-01',
  to: '2024-01-31'
})

// Returns:
{
  total: 450,
  pending: 45,
  resolved: 405,
  upheld: 360,
  overturned: 45,
  overturnRate: 0.111, // 11.1%
  averageResolutionTime: '18h',
  byCategory: {
    harassment: { total: 125, overturned: 12 },
    hate_speech: { total: 80, overturned: 4 },
    // ...
  }
}

Policy Tuning Insights

typescript
const insights = await vettly.getAppealInsights({
  from: '2024-01-01',
  to: '2024-01-31'
})

// Returns suggestions for policy adjustments:
{
  suggestions: [
    {
      category: 'harassment',
      currentThreshold: 0.4,
      suggestedThreshold: 0.45,
      reason: 'High overturn rate (15%) suggests threshold too aggressive',
      affectedDecisions: 250
    }
  ]
}

Compliance

DSA Requirements

The Digital Services Act requires:

  1. Effective complaint handling - Users must be able to contest decisions
  2. Free of charge - Appeals cannot require payment
  3. Timely resolution - Reasonable response times
  4. Human review - Automated decisions must have human review option

Vettly provides the infrastructure to meet all requirements.

Audit Trail

Every appeal is fully documented:

typescript
const audit = await vettly.getAppealAudit('app_xyz789')

// Returns complete history:
[
  { action: 'created', timestamp: '...', user: 'user_123' },
  { action: 'assigned', timestamp: '...', reviewer: 'admin@...' },
  { action: 'viewed', timestamp: '...', reviewer: 'admin@...' },
  { action: 'resolved', timestamp: '...', decision: 'upheld', reason: '...' },
  { action: 'notified', timestamp: '...', channel: 'email' }
]

Next Steps