Skip to content

REST API

Direct HTTP API reference for Vettly moderation.

Base URL

https://api.vettly.dev

Authentication

All requests require an API key in the Authorization header:

http
Authorization: Bearer vettly_xxxxxxxxxxxxx

Content Moderation

Check Content

Check content for moderation violations.

http
POST /v1/check

Request

json
{
  "content": "Text to moderate",
  "policyId": "moderate",
  "contentType": "text",
  "metadata": {
    "userId": "user_123",
    "ip": "192.168.1.1"
  }
}

Response

json
{
  "safe": true,
  "flagged": false,
  "action": "allow",
  "categories": [
    {
      "category": "violence",
      "score": 0.05,
      "threshold": 0.7,
      "triggered": false
    },
    {
      "category": "sexual",
      "score": 0.02,
      "threshold": 0.75,
      "triggered": false
    }
  ],
  "decisionId": "dec_abc123xyz",
  "provider": "openai",
  "latency": 234,
  "cost": 0.001
}

cURL Example

bash
curl -X POST https://api.vettly.dev/v1/check \
  -H "Authorization: Bearer vettly_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello, world!",
    "policyId": "moderate",
    "contentType": "text"
  }'

Dry Run

Test a policy without calling AI providers.

http
POST /v1/check/dry-run

Request

json
{
  "policyId": "moderate",
  "mockScores": {
    "violence": 0.8,
    "sexual": 0.3,
    "hate": 0.95
  }
}

Batch Check (Sync)

Check multiple items synchronously.

http
POST /v1/batch/check

Request

json
{
  "policyId": "moderate",
  "items": [
    {
      "id": "comment_1",
      "content": "Great post!",
      "contentType": "text"
    },
    {
      "id": "comment_2",
      "content": "Inappropriate content",
      "contentType": "text"
    }
  ]
}

Response

json
{
  "items": [
    {
      "id": "comment_1",
      "safe": true,
      "action": "allow",
      "decisionId": "dec_abc123"
    },
    {
      "id": "comment_2",
      "safe": false,
      "action": "block",
      "decisionId": "dec_def456"
    }
  ]
}

Batch Check (Async)

Check multiple items with webhook delivery.

http
POST /v1/batch/check/async

Request

json
{
  "policyId": "moderate",
  "items": [
    { "id": "1", "content": "Text 1" },
    { "id": "2", "content": "Text 2" }
  ],
  "webhookUrl": "https://myapp.com/webhooks/moderation"
}

Response

json
{
  "batchId": "batch_abc123",
  "status": "processing",
  "totalItems": 2
}

Policies

Create Policy

http
POST /v1/policies

Request

json
{
  "policyId": "my_custom_policy",
  "yamlContent": "name: My Policy\ncategories:\n  violence:\n    threshold: 0.7"
}

Get Policy

http
GET /v1/policies/{policyId}

List Policies

http
GET /v1/policies

Response

json
{
  "policies": [
    {
      "policyId": "moderate",
      "name": "Moderate Policy",
      "categories": { ... }
    }
  ]
}

Decisions

Get Decision

http
GET /v1/decisions/{decisionId}

Response

json
{
  "decisionId": "dec_abc123",
  "content": "Original content",
  "safe": true,
  "action": "allow",
  "timestamp": "2025-01-18T10:30:00Z",
  "policyId": "moderate"
}

List Decisions

http
GET /v1/decisions?limit=100&offset=0

Replay Decision

http
POST /v1/decisions/{decisionId}/replay

Request

json
{
  "policyId": "strict"
}

Get cURL Command

http
GET /v1/decisions/{decisionId}/curl

Response

json
{
  "curl": "curl -X POST https://api.vettly.dev/v1/check..."
}

Webhooks

Register Webhook

http
POST /v1/webhooks

Request

json
{
  "url": "https://myapp.com/webhooks/vettly",
  "events": ["decision.created", "decision.flagged", "decision.blocked"],
  "description": "Production webhook"
}

Response

json
{
  "id": "wh_abc123",
  "url": "https://myapp.com/webhooks/vettly",
  "events": ["decision.created", "decision.flagged"],
  "secret": "whsec_xxxxx",
  "enabled": true
}

List Webhooks

http
GET /v1/webhooks

Get Webhook

http
GET /v1/webhooks/{webhookId}

Update Webhook

http
PATCH /v1/webhooks/{webhookId}

Request

json
{
  "enabled": false,
  "events": ["decision.created"]
}

Delete Webhook

http
DELETE /v1/webhooks/{webhookId}

Test Webhook

http
POST /v1/webhooks/{webhookId}/test

Request

json
{
  "eventType": "decision.created"
}

Get Deliveries

http
GET /v1/webhooks/{webhookId}/deliveries?limit=50

Rate Limits

Rate Limit Headers

http
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 299
X-RateLimit-Reset: 1642531200

Rate Limit Exceeded

http
HTTP/1.1 429 Too Many Requests
json
{
  "error": "Rate limit exceeded",
  "retryAfter": 60
}

Error Codes

CodeMeaningSolution
400Bad RequestCheck request format
401UnauthorizedCheck API key
403ForbiddenCheck API key permissions
404Not FoundCheck endpoint/resource ID
429Too Many RequestsImplement rate limiting
500Server ErrorRetry or contact support

Error Response Format

json
{
  "error": "Invalid API key",
  "code": "INVALID_API_KEY",
  "details": "The provided API key is not valid"
}

Content Types

Text

json
{
  "content": "Text to check",
  "contentType": "text"
}

Image (Base64)

json
{
  "content": "/9j/4AAQSkZJRgABAQAAAQ...",
  "contentType": "image"
}

Video

Video moderation is handled through dedicated endpoints. See Video Moderation below.

Multi-Modal Moderation

Check multiple content types in a single request.

Multi-Modal Check

http
POST /v1/check/multimodal

Request

json
{
  "text": "Caption for the post",
  "images": ["https://example.com/image1.jpg", "https://example.com/image2.jpg"],
  "policyId": "moderate",
  "context": {
    "useCase": "social_post",
    "userId": "user_123",
    "userReputation": 0.85,
    "locale": "en-US"
  },
  "metadata": {
    "postId": "post_abc123"
  }
}

Response

json
{
  "decisionId": "dec_abc123",
  "safe": true,
  "flagged": false,
  "action": "allow",
  "results": [
    {
      "contentType": "text",
      "safe": true,
      "flagged": false,
      "action": "allow",
      "categories": [...]
    },
    {
      "contentType": "image",
      "contentRef": "https://example.com/image1.jpg",
      "safe": true,
      "flagged": false,
      "action": "allow",
      "categories": [...]
    }
  ],
  "totalLatency": 450,
  "totalCost": 0.002
}

Video Moderation

Video moderation is asynchronous due to processing time. Results are delivered via webhook.

Check Video by URL

http
POST /v1/video/check

Request

json
{
  "videoUrl": "https://example.com/video.mp4",
  "policyId": "moderate",
  "webhookUrl": "https://myapp.com/webhooks/video",
  "extractionStrategy": {
    "frameInterval": 2,
    "maxFrames": 50
  }
}

Response (202 Accepted)

json
{
  "decisionId": "dec_video123",
  "status": "processing",
  "estimatedTime": 30,
  "estimatedFrames": 25,
  "estimatedCost": 0.025
}

Upload Video File

http
POST /v1/video/upload
Content-Type: multipart/form-data

Request

Form fields:

  • video: Video file (max 100MB)
  • policyId: Policy ID
  • webhookUrl: Webhook URL for results (optional)
  • maxDurationSeconds: Max duration (default: 300)

Get Video Status

http
GET /v1/video/status/{decisionId}

Response

json
{
  "decisionId": "dec_video123",
  "status": "processing",
  "progress": {
    "framesProcessed": 15,
    "framesTotal": 25,
    "percentComplete": 60
  },
  "estimatedCompletion": "2025-01-18T10:35:00Z"
}

Get Video Result

http
GET /v1/video/result/{decisionId}

Response (200 OK when complete)

json
{
  "decisionId": "dec_video123",
  "status": "completed",
  "safe": false,
  "flagged": true,
  "action": "flag",
  "results": [
    {
      "frameNumber": 1,
      "timestamp": 0,
      "safe": true,
      "flagged": false,
      "categories": [...]
    },
    {
      "frameNumber": 15,
      "timestamp": 28.5,
      "safe": false,
      "flagged": true,
      "categories": [
        {
          "category": "violence",
          "score": 0.85,
          "triggered": true
        }
      ],
      "evidence": {
        "url": "https://storage.vettly.dev/evidence/frame-15.jpg",
        "expiresAt": "2025-01-19T10:30:00Z"
      }
    }
  ],
  "totalLatency": 28500,
  "totalCost": 0.025
}

Best Practices

1. Use Idempotency Keys

Prevent duplicate checks:

http
POST /v1/check
Idempotency-Key: unique-request-id

2. Handle Retries

Use exponential backoff for failed requests:

javascript
async function checkWithRetry(content, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetch('/v1/check', { ... })
    } catch (error) {
      if (i === maxRetries - 1) throw error
      await sleep(1000 * Math.pow(2, i))
    }
  }
}

3. Cache Results

Cache decisions by content hash:

javascript
const cacheKey = sha256(content + policyId)
const cached = cache.get(cacheKey)
if (cached) return cached

4. Monitor Costs

Track API usage:

javascript
let totalCost = 0
result.cost && (totalCost += result.cost)
console.log('Total cost today:', totalCost)

5. Use Batch Endpoints

For multiple checks:

javascript
// ❌ Don't do this
for (const comment of comments) {
  await fetch('/v1/check', { body: comment })
}

// ✅ Do this
await fetch('/v1/batch/check', {
  body: { items: comments }
})

Webhooks Integration

See Webhooks documentation for details on:

  • Event types
  • Payload formats
  • Signature verification
  • Retry logic

SDK vs REST API

Use the SDK when:

  • Building Node.js/TypeScript apps
  • Want automatic retries and error handling
  • Need TypeScript types
  • Using Express middleware

Use REST API when:

  • Building in other languages (Python, Ruby, Go)
  • Need direct HTTP control
  • Building serverless functions
  • Integrating with no-code tools

Examples

Python

python
import requests

response = requests.post(
    'https://api.vettly.dev/v1/check',
    headers={
        'Authorization': 'Bearer vettly_xxxxx',
        'Content-Type': 'application/json'
    },
    json={
        'content': 'Text to check',
        'policyId': 'moderate',
        'contentType': 'text'
    }
)

result = response.json()
print('Safe:', result['safe'])

Go

go
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

func checkContent(content string) (bool, error) {
    payload := map[string]string{
        "content": content,
        "policyId": "moderate",
        "contentType": "text",
    }

    body, _ := json.Marshal(payload)

    req, _ := http.NewRequest(
        "POST",
        "https://api.vettly.dev/v1/check",
        bytes.NewBuffer(body),
    )

    req.Header.Set("Authorization", "Bearer vettly_xxxxx")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)

    // Handle response...
}

Ruby

ruby
require 'net/http'
require 'json'

uri = URI('https://api.vettly.dev/v1/check')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer vettly_xxxxx'
request['Content-Type'] = 'application/json'
request.body = {
  content: 'Text to check',
  policyId: 'moderate',
  contentType: 'text'
}.to_json

response = http.request(request)
result = JSON.parse(response.body)

puts "Safe: #{result['safe']}"

See Also

Released under the MIT License.