DoppelDown/API Reference

API Reference

Complete reference for the DoppelDown brand protection API. All endpoints are served from https://doppeldown.com/api.

Base URL: https://doppeldown.com/apiAuth: Supabase Session / Bearer TokenFormat: JSON

Authentication

DoppelDown uses Supabase Auth for authentication. Browser-based requests use session cookies automatically. For programmatic access, include the access token as a Bearer token.

Authorization Header
Authorization: Bearer <supabase_access_token>
Cron endpoints use a separate shared secret: Authorization: Bearer <CRON_SECRET>

Common Auth Errors

StatusCodeDescription
401Missing or invalid session / token
403Valid auth but insufficient permissions (e.g. not admin)

Brands

List Brands

GET/api/brands

Retrieve all brands owned by the authenticated user, ordered by creation date (newest first).

Response 200
[
  {
    "id": "uuid",
    "user_id": "uuid",
    "name": "Acme Corp",
    "domain": "acme.com",
    "keywords": ["acme", "acmecorp"],
    "social_handles": { "twitter": ["@acmecorp"] },
    "enabled_social_platforms": ["twitter", "instagram"],
    "logo_url": "https://...",
    "status": "active",
    "threat_count": 5,
    "created_at": "2025-01-15T00:00:00Z"
  }
]
curl
curl -X GET https://doppeldown.com/api/brands \
  -H "Authorization: Bearer $TOKEN"

Create Brand

POST/api/brands

Create a new brand to monitor. Domain is auto-normalized (strips protocol, www, paths).

ParameterTypeRequiredDescription
namestringRequiredBrand display name
domainstringRequiredPrimary domain (auto-normalized)
keywordsstring[]OptionalAdditional keywords to monitor
social_handlesobjectOptionalMap of platform → handle arrays
enabled_social_platformsstring[]OptionalPlatforms to scan (max depends on tier)
StatusCodeDescription
400Missing name or domain
403BRAND_LIMIT_REACHEDPlan brand limit exceeded
403PLATFORM_LIMIT_EXCEEDEDToo many social platforms for tier
curl
curl -X POST https://doppeldown.com/api/brands \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "domain": "https://www.acme.com",
    "keywords": ["acme", "acmecorp"],
    "social_handles": { "twitter": ["@acmecorp"] },
    "enabled_social_platforms": ["twitter", "instagram"]
  }'

Update Brand

PATCH/api/brands

Update an existing brand. Only provided fields are changed. Social handles can be merged or replaced.

ParameterTypeRequiredDescription
brandIdstringRequiredBrand UUID to update
namestringOptionalNew brand name
domainstringOptionalNew primary domain
keywordsstring[]OptionalReplacement keywords array
social_handlesobjectOptionalSocial handles (merged by default)
enabled_social_platformsstring[]OptionalUpdated enabled platforms
modestringDefault: "merge""merge" or "replace" for social_handles
StatusCodeDescription
400Invalid input or no updates provided
403PLATFORM_LIMIT_EXCEEDEDToo many platforms for tier
404Brand not found or not owned by user
curl
curl -X PATCH https://doppeldown.com/api/brands \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "brandId": "uuid-here",
    "keywords": ["acme", "acmecorp", "acme-inc"]
  }'

Scans

Start Scan

POST/api/scan

Queue a new scan for a brand. Enforces manual scan quotas per tier (free: 3 per 7 days, paid: unlimited).

ParameterTypeRequiredDescription
brandIdstringRequiredBrand UUID to scan
scanTypestringDefault: "full"full | quick | domain_only | web_only | social_only
Response 200
{
  "message": "Scan queued",
  "scanId": "uuid",
  "jobId": "uuid"
}
StatusCodeDescription
400Missing brandId
404Brand not found
409Scan already queued or running for this brand
429QUOTA_EXCEEDEDManual scan quota exceeded (includes quota object)
curl
curl -X POST https://doppeldown.com/api/scan \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "brandId": "uuid-here", "scanType": "full" }'

Get Scan Status

GET/api/scan?id={scanId}

Retrieve details and progress for a specific scan.

ParameterTypeRequiredDescription
idstringRequiredScan UUID (query parameter)
Response 200
{
  "id": "uuid",
  "brand_id": "uuid",
  "scan_type": "full",
  "status": "running",
  "threats_found": 3,
  "domains_checked": 150,
  "pages_scanned": 42,
  "created_at": "2025-01-15T10:00:00Z",
  "completed_at": null,
  "error": null
}
curl
curl -X GET "https://doppeldown.com/api/scan?id=uuid-here" \
  -H "Authorization: Bearer $TOKEN"

Cancel Scan

POST/api/scan/cancel

Cancel a queued or running scan. Sets both the scan and its jobs to cancelled/failed status.

ParameterTypeRequiredDescription
scanIdstringRequiredScan UUID to cancel
Response 200
{ "message": "Scan cancelled", "scanId": "uuid" }
curl
curl -X POST https://doppeldown.com/api/scan/cancel \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "scanId": "uuid-here" }'

Get Scan Quota

GET/api/scan/quota

Check the current user's manual scan quota. Free tier has a 7-day rolling window; paid tiers are unlimited.

Response 200 (Free tier)
{
  "limit": 3,
  "used": 1,
  "remaining": 2,
  "resetsAt": 1705363200000,
  "isUnlimited": false
}
Response 200 (Paid tier / Admin)
{
  "limit": null,
  "used": 0,
  "remaining": null,
  "resetsAt": null,
  "isUnlimited": true
}
curl
curl -X GET https://doppeldown.com/api/scan/quota \
  -H "Authorization: Bearer $TOKEN"

Start Social Scan

POST/api/scan/social

Queue a social-media-only scan targeting specific platforms.

ParameterTypeRequiredDescription
brandIdstringRequiredBrand UUID
platformsstring[]Default: All 8 platformsPlatforms to scan
Response 200
{
  "success": true,
  "message": "Social scan queued",
  "scanId": "uuid",
  "jobId": "uuid",
  "platforms": ["twitter", "instagram", "tiktok"]
}
StatusCodeDescription
409Scan already queued or running for this brand
curl
curl -X POST https://doppeldown.com/api/scan/social \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "brandId": "uuid-here",
    "platforms": ["twitter", "instagram", "tiktok"]
  }'

Delete Scan

DELETE/api/scans/{id}

Permanently delete a scan and cascade-delete all associated threats and evidence files.

This action is irreversible. All threats and evidence screenshots linked to this scan will be permanently deleted.
Response 200
{ "success": true }
curl
curl -X DELETE https://doppeldown.com/api/scans/uuid-here \
  -H "Authorization: Bearer $TOKEN"

Threats

Delete Threat

DELETE/api/threats/{id}

Permanently delete a threat and its evidence files. Creates an audit log entry.

Evidence screenshots are removed from storage on a best-effort basis.
Response 200
{ "success": true }
curl
curl -X DELETE https://doppeldown.com/api/threats/uuid-here \
  -H "Authorization: Bearer $TOKEN"

Evidence

Sign Evidence URL

POST/api/evidence/sign
GET/api/evidence/sign?threatId={id}&kind={kind}

Generate a time-limited signed URL for accessing evidence files (screenshots or HTML snapshots). Supports both GET and POST.

ParameterTypeRequiredDescription
threatIdstringRequiredThreat UUID
kindstringDefault: "screenshot""screenshot" or "html"
indexnumberDefault: 0Index of the evidence item
expiresInnumberDefault: 3600TTL in seconds (60–86400)
Response 200
{
  "signedUrl": "https://supabase-storage/...",
  "expiresIn": 3600,
  "bucket": "evidence",
  "path": "threats/{id}/screenshot-0.png"
}
curl (POST)
curl -X POST https://doppeldown.com/api/evidence/sign \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "threatId": "uuid-here", "kind": "screenshot", "expiresIn": 7200 }'

Reports

Generate Report

POST/api/reports

Generate a takedown report for threats associated with a brand. Returns a downloadable file.

ParameterTypeRequiredDescription
brandIdstringRequiredBrand UUID
threatIdsstring[]Default: All unresolvedSpecific threat UUIDs to include
formatstringDefault: "html"html | text | csv | json
ownerNamestringDefault: User emailName for "brand owner" field
Response is a file download, not JSON. The X-Report-Id header contains the report record UUID.
FormatContent-TypeExtension
htmltext/html.html
texttext/plain.txt
csvtext/csv.csv
jsonapplication/json.txt
curl
curl -X POST https://doppeldown.com/api/reports \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "brandId": "uuid-here",
    "format": "html",
    "ownerName": "Acme Legal Team"
  }' \
  -o takedown-report.html

List Reports

GET/api/reports

List all generated reports, optionally filtered by brand.

ParameterTypeRequiredDescription
brandIdstringOptionalFilter by brand UUID (query parameter)
Response 200
[
  {
    "id": "uuid",
    "brand_id": "uuid",
    "threat_ids": ["uuid-1", "uuid-2"],
    "type": "takedown_request",
    "status": "ready",
    "created_at": "2025-01-15T10:00:00Z",
    "brands": { "user_id": "uuid", "name": "Acme Corp" }
  }
]
curl
curl -X GET "https://doppeldown.com/api/reports?brandId=uuid-here" \
  -H "Authorization: Bearer $TOKEN"

Delete Report

DELETE/api/reports/{id}

Delete a report record. Creates an audit log entry.

Response 200
{ "success": true }
curl
curl -X DELETE https://doppeldown.com/api/reports/uuid-here \
  -H "Authorization: Bearer $TOKEN"

Notifications

Get Notifications

GET/api/notifications

Retrieve up to 50 notifications (newest first) plus the unread count.

Response 200
{
  "notifications": [
    {
      "id": "uuid",
      "user_id": "uuid",
      "type": "threat_detected",
      "title": "New threat found",
      "message": "Suspicious domain acme-corp.xyz detected",
      "read": false,
      "created_at": "2025-01-15T10:00:00Z"
    }
  ],
  "unread_count": 3
}
curl
curl -X GET https://doppeldown.com/api/notifications \
  -H "Authorization: Bearer $TOKEN"

Mark Notifications Read

PATCH/api/notifications

Mark specific notifications or all notifications as read.

ParameterTypeRequiredDescription
idsstring[]OptionalSpecific notification UUIDs to mark read
mark_all_readbooleanOptionalSet true to mark all as read
One of ids or mark_all_read must be provided.
curl (mark all)
curl -X PATCH https://doppeldown.com/api/notifications \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "mark_all_read": true }'
curl (specific IDs)
curl -X PATCH https://doppeldown.com/api/notifications \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["uuid-1", "uuid-2"] }'

Billing (Stripe)

Create Checkout Session

POST/api/stripe/checkout

Start a Stripe Checkout flow to subscribe to a plan. Returns a redirect URL.

ParameterTypeRequiredDescription
planstringRequired"starter", "professional", or "enterprise"
Response 200
{ "url": "https://checkout.stripe.com/c/pay/..." }
StatusCodeDescription
400Invalid plan or Stripe price ID not configured
curl
curl -X POST https://doppeldown.com/api/stripe/checkout \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "plan": "professional" }'

Create Portal Session

POST/api/stripe/portal

Open the Stripe Customer Portal for managing subscriptions, invoices, and payment methods.

Response 200
{ "url": "https://billing.stripe.com/p/session/..." }
StatusCodeDescription
400No subscription found (user has no Stripe customer ID)
curl
curl -X POST https://doppeldown.com/api/stripe/portal \
  -H "Authorization: Bearer $TOKEN"

Stripe Webhook

POST/api/stripe/webhook

Receives Stripe webhook events. Configured in the Stripe Dashboard — not called directly by clients.

Authenticated via stripe-signature header, not Bearer tokens.
EventAction
checkout.session.completedActivates subscription, sets tier
customer.subscription.updatedUpdates subscription status and tier
customer.subscription.deletedResets user to free tier
invoice.payment_failedSets subscription to past_due

Admin

Get Audit Logs

GET/api/admin/audit-logs

Retrieve system audit logs. Admin only — requires the is_admin flag on the user record.

ParameterTypeRequiredDescription
entity_typestringOptionalFilter by entity: scan, threat, report
user_idstringOptionalFilter by user UUID
limitnumberDefault: 100Results per page (max 500)
offsetnumberDefault: 0Pagination offset
Response 200
{
  "logs": [
    {
      "id": "uuid",
      "user_id": "uuid",
      "user_email": "admin@example.com",
      "action": "DELETE",
      "entity_type": "threat",
      "entity_id": "uuid",
      "metadata": {
        "brand_id": "uuid",
        "threat_type": "typosquat_domain",
        "severity": "high"
      },
      "created_at": "2025-01-15T10:00:00Z"
    }
  ],
  "total": 42,
  "limit": 100,
  "offset": 0
}
StatusCodeDescription
403User is not an admin
curl
curl -X GET "https://doppeldown.com/api/admin/audit-logs?entity_type=scan&limit=50" \
  -H "Authorization: Bearer $TOKEN"

Cron Jobs

These endpoints are called by an external scheduler (e.g. Vercel Cron) and require Authorization: Bearer <CRON_SECRET>.

Automated Scan Scheduler

GET/api/cron/scan

Queues automated scans for all active brands based on tier-specific scan frequency. Adds random jitter (0–5 min) to spread load.

TierScan Frequency
FreeSkipped (manual only)
StarterEvery 24 hours
ProfessionalEvery 6 hours
EnterpriseEvery 1 hour
Response 200
{
  "success": true,
  "results": { "queued": 12, "skipped": 5, "errors": [] },
  "timestamp": "2025-01-15T10:00:00Z"
}

Weekly Digest

GET/api/cron/digest

Sends weekly email digests to users with email alerts enabled. Aggregates threats from the past 7 days per brand (max 10 per brand to avoid email clipping).

Response 200
{
  "success": true,
  "results": { "sent": 8, "skipped": 3, "errors": [] },
  "timestamp": "2025-01-15T10:00:00Z"
}

NRD Monitor

GET/api/cron/nrd

Processes newly registered domains against enterprise brands for typosquatting detection. Max execution time: 5 minutes. Enterprise tier only.

Response 200
{
  "success": true,
  "results": {
    "domainsProcessed": 50000,
    "matchesFound": 3,
    "threatsCreated": 1,
    "errors": []
  },
  "processingTimeMs": 45000,
  "timestamp": "2025-01-15T10:00:00Z"
}

Tier Limits Reference

FeatureFreeStarterProfessionalEnterprise
Brands1310Unlimited
Domain variations / scan251005002,500
Social platforms1368 (all)
Automated scan frequencyManual onlyEvery 24hEvery 6hEvery 1h
Manual scans3 / 7 daysUnlimitedUnlimitedUnlimited
NRD monitoring

Available social platforms: twitter, facebook, instagram, linkedin, tiktok, youtube, telegram, discord

Error Codes

All error responses follow this format:

{ "error": "Human-readable message", "code": "MACHINE_READABLE_CODE" }

Application Error Codes

StatusCodeDescription
403BRAND_LIMIT_REACHEDUser has reached their plan's brand limit
403PLATFORM_LIMIT_EXCEEDEDToo many social platforms selected for tier
429QUOTA_EXCEEDEDManual scan quota depleted for the current period

HTTP Status Codes

StatusMeaning
200Success
400Bad request / validation error
401Not authenticated
403Forbidden / tier limit reached
404Resource not found
409Conflict (e.g. duplicate scan)
429Rate limited / quota exceeded
500Internal server error

DoppelDown API Documentation — doppeldown.com