API Reference
Complete reference for the DoppelDown brand protection API. All endpoints are served from https://doppeldown.com/api.
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: Bearer <supabase_access_token>Authorization: Bearer <CRON_SECRET>Common Auth Errors
| Status | Code | Description |
|---|---|---|
| 401 | — | Missing or invalid session / token |
| 403 | — | Valid auth but insufficient permissions (e.g. not admin) |
Brands
List Brands
Retrieve all brands owned by the authenticated user, ordered by creation date (newest first).
[
{
"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 -X GET https://doppeldown.com/api/brands \
-H "Authorization: Bearer $TOKEN"Create Brand
Create a new brand to monitor. Domain is auto-normalized (strips protocol, www, paths).
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Brand display name |
| domain | string | Required | Primary domain (auto-normalized) |
| keywords | string[] | Optional | Additional keywords to monitor |
| social_handles | object | Optional | Map of platform → handle arrays |
| enabled_social_platforms | string[] | Optional | Platforms to scan (max depends on tier) |
| Status | Code | Description |
|---|---|---|
| 400 | — | Missing name or domain |
| 403 | BRAND_LIMIT_REACHED | Plan brand limit exceeded |
| 403 | PLATFORM_LIMIT_EXCEEDED | Too many social platforms for tier |
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
Update an existing brand. Only provided fields are changed. Social handles can be merged or replaced.
| Parameter | Type | Required | Description |
|---|---|---|---|
| brandId | string | Required | Brand UUID to update |
| name | string | Optional | New brand name |
| domain | string | Optional | New primary domain |
| keywords | string[] | Optional | Replacement keywords array |
| social_handles | object | Optional | Social handles (merged by default) |
| enabled_social_platforms | string[] | Optional | Updated enabled platforms |
| mode | string | Default: "merge" | "merge" or "replace" for social_handles |
| Status | Code | Description |
|---|---|---|
| 400 | — | Invalid input or no updates provided |
| 403 | PLATFORM_LIMIT_EXCEEDED | Too many platforms for tier |
| 404 | — | Brand not found or not owned by user |
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"]
}'Upload Brand Logo
Upload a PNG logo for a brand. Max file size: 5 MB.
multipart/form-data. Only PNG images are accepted.| Parameter | Type | Required | Description |
|---|---|---|---|
| brandId | string | Required | Brand UUID (form field) |
| logo | File | Required | PNG image file (max 5MB) |
{
"logo_url": "https://supabase-storage-url/...",
"storage_path": "brands/{brandId}/logo/{timestamp}-{uuid}-{name}.png"
}curl -X POST https://doppeldown.com/api/brands/logo \
-H "Authorization: Bearer $TOKEN" \
-F "brandId=uuid-here" \
-F "logo=@/path/to/logo.png"Delete Brand Logo
Remove a brand's logo from storage and clear the URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| brandId | string | Required | Brand UUID |
curl -X DELETE https://doppeldown.com/api/brands/logo \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "brandId": "uuid-here" }'Scans
Start Scan
Queue a new scan for a brand. Enforces manual scan quotas per tier (free: 3 per 7 days, paid: unlimited).
| Parameter | Type | Required | Description |
|---|---|---|---|
| brandId | string | Required | Brand UUID to scan |
| scanType | string | Default: "full" | full | quick | domain_only | web_only | social_only |
{
"message": "Scan queued",
"scanId": "uuid",
"jobId": "uuid"
}| Status | Code | Description |
|---|---|---|
| 400 | — | Missing brandId |
| 404 | — | Brand not found |
| 409 | — | Scan already queued or running for this brand |
| 429 | QUOTA_EXCEEDED | Manual scan quota exceeded (includes quota object) |
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
Retrieve details and progress for a specific scan.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Scan UUID (query parameter) |
{
"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 -X GET "https://doppeldown.com/api/scan?id=uuid-here" \
-H "Authorization: Bearer $TOKEN"Cancel Scan
Cancel a queued or running scan. Sets both the scan and its jobs to cancelled/failed status.
| Parameter | Type | Required | Description |
|---|---|---|---|
| scanId | string | Required | Scan UUID to cancel |
{ "message": "Scan cancelled", "scanId": "uuid" }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
Check the current user's manual scan quota. Free tier has a 7-day rolling window; paid tiers are unlimited.
{
"limit": 3,
"used": 1,
"remaining": 2,
"resetsAt": 1705363200000,
"isUnlimited": false
}{
"limit": null,
"used": 0,
"remaining": null,
"resetsAt": null,
"isUnlimited": true
}curl -X GET https://doppeldown.com/api/scan/quota \
-H "Authorization: Bearer $TOKEN"Delete Scan
Permanently delete a scan and cascade-delete all associated threats and evidence files.
{ "success": true }curl -X DELETE https://doppeldown.com/api/scans/uuid-here \
-H "Authorization: Bearer $TOKEN"Threats
Delete Threat
Permanently delete a threat and its evidence files. Creates an audit log entry.
{ "success": true }curl -X DELETE https://doppeldown.com/api/threats/uuid-here \
-H "Authorization: Bearer $TOKEN"Evidence
Sign Evidence URL
Generate a time-limited signed URL for accessing evidence files (screenshots or HTML snapshots). Supports both GET and POST.
| Parameter | Type | Required | Description |
|---|---|---|---|
| threatId | string | Required | Threat UUID |
| kind | string | Default: "screenshot" | "screenshot" or "html" |
| index | number | Default: 0 | Index of the evidence item |
| expiresIn | number | Default: 3600 | TTL in seconds (60–86400) |
{
"signedUrl": "https://supabase-storage/...",
"expiresIn": 3600,
"bucket": "evidence",
"path": "threats/{id}/screenshot-0.png"
}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
Generate a takedown report for threats associated with a brand. Returns a downloadable file.
| Parameter | Type | Required | Description |
|---|---|---|---|
| brandId | string | Required | Brand UUID |
| threatIds | string[] | Default: All unresolved | Specific threat UUIDs to include |
| format | string | Default: "html" | html | text | csv | json |
| ownerName | string | Default: User email | Name for "brand owner" field |
X-Report-Id header contains the report record UUID.| Format | Content-Type | Extension |
|---|---|---|
| html | text/html | .html |
| text | text/plain | .txt |
| csv | text/csv | .csv |
| json | application/json | .txt |
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.htmlList Reports
List all generated reports, optionally filtered by brand.
| Parameter | Type | Required | Description |
|---|---|---|---|
| brandId | string | Optional | Filter by brand UUID (query parameter) |
[
{
"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 -X GET "https://doppeldown.com/api/reports?brandId=uuid-here" \
-H "Authorization: Bearer $TOKEN"Delete Report
Delete a report record. Creates an audit log entry.
{ "success": true }curl -X DELETE https://doppeldown.com/api/reports/uuid-here \
-H "Authorization: Bearer $TOKEN"Notifications
Get Notifications
Retrieve up to 50 notifications (newest first) plus the unread count.
{
"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 -X GET https://doppeldown.com/api/notifications \
-H "Authorization: Bearer $TOKEN"Mark Notifications Read
Mark specific notifications or all notifications as read.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ids | string[] | Optional | Specific notification UUIDs to mark read |
| mark_all_read | boolean | Optional | Set true to mark all as read |
ids or mark_all_read must be provided.curl -X PATCH https://doppeldown.com/api/notifications \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "mark_all_read": true }'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
Start a Stripe Checkout flow to subscribe to a plan. Returns a redirect URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| plan | string | Required | "starter", "professional", or "enterprise" |
{ "url": "https://checkout.stripe.com/c/pay/..." }| Status | Code | Description |
|---|---|---|
| 400 | — | Invalid plan or Stripe price ID not configured |
curl -X POST https://doppeldown.com/api/stripe/checkout \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "plan": "professional" }'Create Portal Session
Open the Stripe Customer Portal for managing subscriptions, invoices, and payment methods.
{ "url": "https://billing.stripe.com/p/session/..." }| Status | Code | Description |
|---|---|---|
| 400 | — | No subscription found (user has no Stripe customer ID) |
curl -X POST https://doppeldown.com/api/stripe/portal \
-H "Authorization: Bearer $TOKEN"Stripe Webhook
Receives Stripe webhook events. Configured in the Stripe Dashboard — not called directly by clients.
stripe-signature header, not Bearer tokens.| Event | Action |
|---|---|
| checkout.session.completed | Activates subscription, sets tier |
| customer.subscription.updated | Updates subscription status and tier |
| customer.subscription.deleted | Resets user to free tier |
| invoice.payment_failed | Sets subscription to past_due |
Admin
Get Audit Logs
Retrieve system audit logs. Admin only — requires the is_admin flag on the user record.
| Parameter | Type | Required | Description |
|---|---|---|---|
| entity_type | string | Optional | Filter by entity: scan, threat, report |
| user_id | string | Optional | Filter by user UUID |
| limit | number | Default: 100 | Results per page (max 500) |
| offset | number | Default: 0 | Pagination offset |
{
"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
}| Status | Code | Description |
|---|---|---|
| 403 | — | User is not an admin |
curl -X GET "https://doppeldown.com/api/admin/audit-logs?entity_type=scan&limit=50" \
-H "Authorization: Bearer $TOKEN"Cron Jobs
Authorization: Bearer <CRON_SECRET>.Automated Scan Scheduler
Queues automated scans for all active brands based on tier-specific scan frequency. Adds random jitter (0–5 min) to spread load.
| Tier | Scan Frequency |
|---|---|
| Free | Skipped (manual only) |
| Starter | Every 24 hours |
| Professional | Every 6 hours |
| Enterprise | Every 1 hour |
{
"success": true,
"results": { "queued": 12, "skipped": 5, "errors": [] },
"timestamp": "2025-01-15T10:00:00Z"
}Weekly 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).
{
"success": true,
"results": { "sent": 8, "skipped": 3, "errors": [] },
"timestamp": "2025-01-15T10:00:00Z"
}NRD Monitor
Processes newly registered domains against enterprise brands for typosquatting detection. Max execution time: 5 minutes. Enterprise tier only.
{
"success": true,
"results": {
"domainsProcessed": 50000,
"matchesFound": 3,
"threatsCreated": 1,
"errors": []
},
"processingTimeMs": 45000,
"timestamp": "2025-01-15T10:00:00Z"
}Tier Limits Reference
| Feature | Free | Starter | Professional | Enterprise |
|---|---|---|---|---|
| Brands | 1 | 3 | 10 | Unlimited |
| Domain variations / scan | 25 | 100 | 500 | 2,500 |
| Social platforms | 1 | 3 | 6 | 8 (all) |
| Automated scan frequency | Manual only | Every 24h | Every 6h | Every 1h |
| Manual scans | 3 / 7 days | Unlimited | Unlimited | Unlimited |
| 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
| Status | Code | Description |
|---|---|---|
| 403 | BRAND_LIMIT_REACHED | User has reached their plan's brand limit |
| 403 | PLATFORM_LIMIT_EXCEEDED | Too many social platforms selected for tier |
| 429 | QUOTA_EXCEEDED | Manual scan quota depleted for the current period |
HTTP Status Codes
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request / validation error |
| 401 | Not authenticated |
| 403 | Forbidden / tier limit reached |
| 404 | Resource not found |
| 409 | Conflict (e.g. duplicate scan) |
| 429 | Rate limited / quota exceeded |
| 500 | Internal server error |
DoppelDown API Documentation — doppeldown.com